BUILDING.txt0100644 0000000 0000000 00000002377 00000250600 011532 0ustar000000000 0000000 Building HttpComponents Core ============================ (1) Requisites -------------- JDK 1.7+ is required in order to compile and run HttpCore. HttpCore utilizes Maven as a distribution management and packaging tool. Version 3.3 or later is required. Maven installation and configuration instructions can be found here: http://maven.apache.org/run-maven/index.html (2) Executing test cases Execute the following command in order to compile and test the components: mvn test To avoid intermittent test failures, it is recommended to have a machine or virtual machine with the following specifications: Minimum of 2 CPUs. Minimum of 2GB of RAM. These are the minimal requirements for stable test execution. In more resource constrained environments some tests may fail intermittently. (3) Building packages Execute the following command in order to build the JAR packages and install them to the local repository: mvn install The JAR packages can be found in the target folders of their respective modules httpcore5/target/httpcore5-.jar httpcore5-h2/target/httpcore5-h2-.jar httpcore5-testing/target/httpcore5-testing-.jar httpcore5-osgi/target/org.apache.httpcomponents.httpcore_.jar where is the release version CODE_OF_CONDUCT.md0100644 0000000 0000000 00000001641 00000250600 012364 0ustar000000000 0000000 The Apache code of conduct page is [https://www.apache.org/foundation/policies/conduct.html](https://www.apache.org/foundation/policies/conduct.html). LICENSE.txt0100644 0000000 0000000 00000023677 00000250600 011425 0ustar000000000 0000000 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 NOTICE.txt0100644 0000000 0000000 00000000264 00000250600 011307 0ustar000000000 0000000 Apache HttpComponents Core Copyright 2005-2024 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). README.md0100644 0000000 0000000 00000010250 00000250600 011040 0ustar000000000 0000000 Apache HttpComponents Core ========================== Welcome to the HttpCore component of the Apache HttpComponents project. [![GitHub Actions Status](https://github.com/apache/httpcomponents-core/workflows/Java%20CI/badge.svg)](https://github.com/apache/httpcomponents-core/actions) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.httpcomponents.core5/httpcore5/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.apache.httpcomponents.core5/httpcore5) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) Building Instructions --------------------- For building from source instructions please refer to [BUILDING.txt](./BUILDING.txt). Dependencies ------------ HttpCore requires Java 1.8 compatible runtime. Protocol conformance -------------------- - - [RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110) - HTTP Semantics - [RFC 9112](https://datatracker.ietf.org/doc/html/rfc9112) - Hypertext Transfer Protocol Version 1.1 (HTTP/1.1) - [RFC 9113](https://datatracker.ietf.org/doc/html/rfc9113) - Hypertext Transfer Protocol Version 2 (HTTP/2) - [RFC 7541](https://datatracker.ietf.org/doc/html/rfc7541) - HPACK: Header Compression for HTTP/2 - [RFC 1945](https://datatracker.ietf.org/doc/html/rfc1945) - Hypertext Transfer Protocol -- HTTP/1.0 - [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986) - Uniform Resource Identifier (URI): Generic Syntax - [RFC 9218](https://datatracker.ietf.org/doc/html/rfc9218) - Extensible Prioritization Scheme for HTTP Licensing --------- Apache HttpComponents Core is licensed under the Apache License 2.0. See the files [LICENSE.txt](./LICENSE.txt) and [NOTICE.txt](./NOTICE.txt) for more information. Contact ------- - For general information visit the main project site at https://hc.apache.org/ - For current status information visit the status page at https://hc.apache.org/status.html - If you want to contribute visit https://hc.apache.org/get-involved.html Cryptographic Software Notice ----------------------------- This distribution may include software that has been designed for use with cryptographic software. The country in which you currently reside may have restrictions on the import, possession, use, and/or re-export to another country, of encryption software. BEFORE using any encryption software, please check your country's laws, regulations and policies concerning the import, possession, or use, and re-export of encryption software, to see if this is permitted. See https://www.wassenaar.org/ for more information. The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS), has classified this software as Export Commodity Control Number (ECCN) 5D002.C.1, which includes information security software using or performing cryptographic functions with asymmetric algorithms. The form and manner of this Apache Software Foundation distribution makes it eligible for export under the License Exception ENC Technology Software Unrestricted (TSU) exception (see the BIS Export Administration Regulations, Section 740.13) for both object code and source code. The following provides more details on the included software that may be subject to export controls on cryptographic software: > Apache HttpComponents Core interfaces with the > Java Secure Socket Extension (JSSE) API to provide > - HTTPS support > > Apache HttpComponents Core does not include any > implementation of JSSE. RELEASE_NOTES.txt0100644 0000000 0000000 00000465744 00000250600 012400 0ustar000000000 0000000 Release 5.4.3 ------------------ This maintenance release fixes several defects and regression reported sicne the previous release includin a regression in backpressure handling of async TLS sessions introduced in version 5.3.3. Change Log ------------------- * HTTPCORE-796: abort redirected requests during the expect-continue handshake the same way as errors. Contributed by Oleg Kalnichevski * Fixed regression in backpressure handling of async TLS sessions. Reverts HTTPCORE-775. Contributed by Ryan Schmitt * Use max line length of 8192 and max header count of 100 for incoming HTTP/1 messages by default. Contributed by Oleg Kalnichevski * Enforce configured HPACK header list size limit upon initialization of HTTP/2 connections. Contributed by Arturo Bernal * HTTPCORE-774: fixed a race condition caused by concurrent update of the connection input window to the max value (ported from 5.3.x; omitted by mistake). Contributed by Oleg Kalnichevski * Ensure async data consumers can avoid NPE if they have been canceled or released from another thread at the same with concurrent data processing. Contributed by Oleg Kalnichevski * Fixed connection pool lease timeout race potentially causing pool entry leak (#649). Contributed by Arturo Bernal Release 5.4.2 ------------------ This maintenance release fixes a bug in the lax connection pool where expired connection cleanup fails to update the number of available connections, potentially causing the client to eventually deadlock. Two other fixes are included as well, one for a memory leak in the implementation of HTTP/2 stream priority and one in the parseLenient method, which should ignore illegal character names. Change Log ------------------- * Bug fix: Decrement pool entry count in LaxConnPool upon discarding an expired connection. Contributed by Ryan Schmitt * Bug fix: Fix HTTP/2 stream priority memory leak. (#617) Contributed by Arturo Bernal * Bug fix: Fix parseLenient to ignore illegal charset names. (#618) Contributed by Arturo Bernal Release 5.4.1 ------------------ This maintenance release fixes a latency regression in the async transport caused by TCP_NODELAY failing to be set. Several other bugs have also been fixed affecting connection management, HTTP/2 exception handling and propagation, and so forth. Change Log ------------------- * Regression: Ensure TCP-specific socket options are set correctly in the async transport. Contributed by Ryan Schmitt * Bug fix: Prevent integer overflow in LaxConnPool so that negative numbers are not reported in the thread pool statistics. Contributed by Ryan Schmitt * Bug fix: Loop over and close expired connections in LaxConnPool instead of returning them. Contributed by Ryan Schmitt * Bug fix: Respect the PoolReusePolicy.FIFO in StrictConnPool. Contributed by Ryan Schmitt * Bug fix: Corrected exception propagation in protocol negotiators' exception handling code. Contributed by Oleg Kalnichevski * Bug fix: Fix SOCKS handshake to fail on EOF. (#604) Contributed by Arturo Bernal * Bug fix: Handle CancelledKeyException thrown by abort method of the H2 stream. Contributed by Oleg Kalnichevski * Bug fix: Fix race condition in ComplexCancellable. Contributed by Ryan Schmitt * Bug fix: In MonitoringResponseOutOfOrderStrategy, always perform a blocking read to check for data. Contributed by Ryan Schmitt * Bug fix: Corrected exception propagation to individual H2 streams in case of an unexpected error with the H2 connection. Contributed by Oleg Kalnichevski * Regression: Restored SocketSupport and marked it deprecated. Contributed by Oleg Kalnichevski Release 5.4 ------------------ This is the first GA release in the 5.4 release series that improves HTTP/2 protocol support by ensuring conformance to the latest HTTP specification (RFC 9113) and adds support for Unix domain sockets. Notable changes and features included in the 5.4 series: * Experimental OFFLOCK (lock-free) connection pool. * Conformance to RFC 9218 (Extensible Prioritization Scheme for HTTP). * Improved conformance to RFC 9113 (Hypertext Transfer Protocol Version 2). * Five-second TCP keep-alive enabled by default. * Unix domain socket support by the classic and async transports. * Redesign of classic over async API bridge. * Improved URI encoding per RFC 3986. * QUERY method support. * Improved idle connection validation in the async transport. Change Log ------------------- * Fix validation of 0/8 IPv4 addresses. Contributed by Ryan Schmitt * Route-segmented pool: fix timeout race; add direct hand-off; enforce TTL on lease; bound scans (#575). Contributed by Arturo Bernal * Ensure connection is closed immediately upon socket timeout. Contributed by Alexis Le Dantec * Accept leading zeros in IPv4-mapped IPv6 (#568). Contributed by Arturo Bernal Release 5.4-alpha1 ------------------ This is the first ALPHA release in the 5.4 release series that improves HTTP/2 protocol support by ensuring conformance to the latest HTTP specification (RFC 9113) and adds support for Unix domain sockets. This release also includes all the fixes from the stable 5.3 branch. Change Log ------------------- * Experimental RouteSegmentedConnPool (OFFLOCK): lock-free, route-segmented, disposal off critical path. Contributed by Arturo Bernal * Configurable AuthorityResolver for async server bootstraps. Contributed by Christian de Waal * RFC 9218 HTTP/2 Priority support (#552). Contributed by Arturo Bernal * Stale connection check for the async protocol handlers. Contributed by Ryan Schmitt * Corrected JavaDoc for HttpConnection#close to reference correct CloseMode mode (#561). Contributed by Arturo Bernal * HTTPCORE-785: Improved Javadocs of TlsStrategy implementations. Contributed by Oleg Kalnichevski * HTTPCORE-785: Updated request execution example with Conscrypt as a security provider. Contributed by Oleg Kalnichevski * Five-second TCP keep-alive enabled by default. Contributed by Ryan Schmitt * RFC 9113 conformance: H2 stream can be cancelled (reset) locally from multiple threads. Contributed by Oleg Kalnichevski * RFC 9113 conformance: Strict SETTINGS_MAX_CONCURRENT_STREAMS enforcement for inbound and outbound streams. Contributed by Oleg Kalnichevski * RFC 9113 conformance: Use command queue to activate streams reserved with a push promise. Contributed by Oleg Kalnichevski * RFC 9113 conformance: Improved H2 stream creation and initialization, improved H2 stream state tracking and tracking of total stream counts. Contributed by Oleg Kalnichevski * RFC 9113 conformance: Corrected H2 stream ID generation and verification logic. Contributed by Oleg Kalnichevski * RFC 9113 conformance: Refactoring of internal H2 stream APIs. Contributed by Oleg Kalnichevski * RFC 9113 conformance: Merge multiple Cookie Header fields into a single field after decompression. Contributed by Oleg Kalnichevski * RFC 9113 conformance: Stricter validation of message fields. Contributed by Oleg Kalnichevski * RFC 9113 conformance: RST_STREAM with NO_ERROR error code gracefully terminates the request on the client if the server has sent the complete response (the stream has been closed remotely). Contributed by Oleg Kalnichevski * Revised graceful / immediate H2 connection shutdown. Contributed by Oleg Kalnichevski * RFC 9113 conformance: Ensure the ID of promised streams is valid (greater than the last seen). Contributed by Oleg Kalnichevski * RFC 9113 conformance: Treat ENABLE_PUSH set to 1 transmitted by the server endpoint as a protocol error. Contributed by Oleg Kalnichevski * RFC 9113 conformance: Treat attempts to send frames on a locally closed stream as a protocol violation. Contributed by Oleg Kalnichevski * HTTP/1.1 async protocol handler to read message data more greedily. Contributed by Oleg Kalnichevski * Optimized release of resources associated with H2 streams. #releaseResources should be called only once. Contributed by Oleg Kalnichevski * H2 transport to enforce a max limit on the number of CONTINUATION frames. Contributed by Oleg Kalnichevski * Socket keep-alive option support. Contributed by Ryan Schmitt * Discontinue use of SocketSupport to set extended socket options. Contributed by Ryan Schmitt * IOFunction functional interface. Contributed by Arturo Bernal * Safeguard for infinite unproductive loop in non-blocking SSL session #527. Contributed by Ali Farahani * Slightly better and efficient #toHexString method. Contributed by Oleg Kalnichevski * HTTP/2 protocol handler to allow configuring FrameFractory (#525). Contributed by Marko * ContentType.APPLICATION_ZIP_COMPRESSED (#524). Contributed by Gary Gregory * I/O reactors to support Unix domain sockets. Contributed by Ryan Schmitt * Redesign of classic over async bridge. Contributed by Oleg Kalnichevski * Shared buffers to support timeout on blocking operations. Contributed by Oleg Kalnichevski * Added a method to the async response channel to terminate the ongoing message exchange due to an internal error. Contributed by Oleg Kalnichevski * Avoid legacy Socket API in reactor code. Contributed by Ryan Schmitt * Support for parsing of headers representing a list of complex elements. Contributed by Oleg Kalnichevski * RFC 3986 conformant URI encoding policy (#517). Contributed by Arturo Bernal * Protocol negotiation handlers to use exception callback to report unexpected exceptions during the protocol negotiation handshake. Contributed by Oleg Kalnichevski * Updated error message in ProtocolVersion. Contributed by Mavelous * Handle overflow in Deadline calculation to ensure correct behavior for large time values (#512). Contributed by Arturo Bernal * HTTPCORE-774: Update window management improvements. Contributed by Oleg Kalnichevski * Added more convenience methods to classic request / response builders. Contributed by Oleg Kalnichevski * HTTPCORE-773: Introduced customizable strategy to select I/O dispatch worker for new I/O channels. Contributed by Oleg Kalnichevski * Optimize connection close logic to resolve timeout delay issue (#508). Contributed by Noah * NoConnectionReuseStrategy Contributed by Johnny Lim * Polish Javadoc for HttpResponseInterceptor. Contributed by Johnny Lim * HTTP Method QUERY support (#499). Contributed by Mario Daniel Ruiz Saavedra * Ensure Date header is always present and replace invalid values. (#500). Contributed by Arturo Bernal * HTTP/1.1 TE header interceptor and strict client/server option (#498). Contributed by Arturo Bernal * HTTPCLIENT-2233: Metrics listener for IOReactor thread pool monitoring (#490). Contributed by Arturo Bernal * A FutureContribution should not allow a null BasicFuture (#485). Contributed by Gary Gregory * HTTPCORE-770: Introduced `plusAsBlank` parameter for dynamic configuration. Contributed by Arturo Bernal Release 5.3 ------------------ This is the first GA release in the 5.3 release series. This release finalizes the 5.3 APIs and improves integration test configuration APIs along with many test code improvements. Notable changes and features included in the 5.3 series: * Improved conformance to RFC 9110 and RFC 9112. * UTF-8 encoding to be used by default for text where appropriate. * Compatibility with Java Virtual Threads and Java 21 Runtime. * Message parsing API improvements and performance optimization. * TLS client endpoints to make use of JSSE Endpoint Identification by default. * Redesign of server-side request routing API. * Redesign of TLS session handling by classic (blocking) connections. Change Log ------------------- * Improved Javadoc. Contributed by Gary Gregory * HTTPCORE-761: Support ExtendedSocketOption (#473). Contributed by kkewwei * Attempt to shut down the connection gracefully in case of a TLS handshake exception. Contributed by Oleg Kalnichevski * Test classes and methods should have default package visibility. Contributed by strangelookingnerd <49242855+strangelookingnerd at users.noreply.github.com> * Simplified configuration API of test servers and clients. Contributed by Oleg Kalnichevski Release 5.3 BETA1 ------------------ This is the first BETA release in the 5.3 release series. It features a re-designed classic (blocking) TLS session management and server-side request routing APIs. It is likely to be the last BETA release in the 5.3 release series. Change Log ------------------- * Improved Javadoc. Contributed by Gary Gregory * HTTPCLIENT-2328: Blocking i/o connections to check if the opposite TLS endpoint has been closed by the opposite endpoint while writing out request body. Contributed by Oleg Kalnichevski * HTTPCLIENT-2328: Better control over the TLS layer of blocking connections. TLS layer no longer automatically closes the underlying network socket. Connections close the network socket manually after TLS session termination. Contributed by Oleg Kalnichevski * More flexible TLS layer initialization by the classic HTTP server bootstrap. Contributed by Oleg Kalnichevski * HTTPCORE-766: redesign of server-side request routing. Contributed by Oleg Kalnichevski * Bug fix: corrected exception type thrown in case of unexpected connection termination. Contributed by Oleg Kalnichevski * Made HttpCoreContext consistent with the behavior of HttpContext implementations in HttpClient. Contributed by Oleg Kalnichevski * Bug fix: HTTP/1.1 server-side stream handler to validate the request message before the request routing and handler resolution. Contributed by Oleg Kalnichevski * HPackDecoder incorrectly calculates required buffer length causing G1 Humongous Allocation and OOM (#465). Contributed by crazylulululu <42406448+crazylulululu at users.noreply.github.com> * Update the method parameter of InetAddressUtils from String to CharSequence. Contributed by wangkai Release 5.3 ALPHA2 ------------------ This is the second and likely the last ALPHA release in the 5.3 release series. It finalizes the API changes introduced in the previous ALPHA release and also improves Message Parsing APIs and client-side TLS defaults by making use of JSSE Endpoint Identification. Change Log ------------------- * HttpCoreContext to use instance variables for standard attributes. Contributed by Oleg Kalnichevski * TLS client endpoints to make use of JSSE Endpoint Identification by default. Contributed by Oleg Kalnichevski * Mention HttpStatus and StatusCode in JavaDoc in more places (#454). Contributed by Dmitrii Naumenko * Improved protocol version parsing. Contributed by Oleg Kalnichevski * Replaced token delimitation based on BitSet with a predicate function. Contributed by Oleg Kalnichevski * HTTPCORE-763: remove checks that assert a path does not start with "//". Contributed by Marco Bungart * Corrected declaration of generic Header iterators. Contributed by Oleg Kalnichevski Release 5.3 ALPHA1 ------------------ This is the first ALPHA release in the 5.3 release series that improves HTTP protocol support by ensuring conformance to the latest HTTP specification (RFC 9110 and RFC 9112) and also ensuring compatibility with Java Virtual Threads by replacing 'synchronized' keywords in critical sections with Java lock primitives. Change Log ------------------- * Removed AccessController checks (AccessController deprecated in Java 21). Contributed by Oleg Kalnichevski * Support for percent coding as defined in RFC 5987. Contributed by Arturo Bernal * HTTPCORE-756: Improved Transfer-Encoding handling and message frame validity verification per RFC 9112 section 6.1. Contributed by Oleg Kalnichevski * HTTPCORE-756: Stricter parsing of response status code per RFC 9112 section 3.2; less intermediate garbage while parsing response status lines. Contributed by Oleg Kalnichevski * HTTPCORE-756: Support for the effective HTTP/1.x protocol level config parameter; HTTP/1.1 endpoints to signal their actual supported protocol level (the minor version in HTTP/1.x) in the message control data as per RFC 9110 section 6.2. Contributed by Oleg Kalnichevski * HTTPCORE-756: Authority from an absolute request URI to take precedence over Host header per RFC 9112 section 3.2. Contributed by Oleg Kalnichevski * HTTPCORE-756: Client protocol handlers to try to send `Host` as the first header in the request header section per RFC 9110 section 7.2. Contributed by Oleg Kalnichevski * HTTPCORE-756: Updated response status codes as per RFC 9110 section 18.3. Contributed by Oleg Kalnichevski * HTTPCORE-756: Expect-Continue handshake improvements per RFC 9110 section 10.1.1. Contributed by Oleg Kalnichevski * Additional message support methods. Contributed by Oleg Kalnichevski * HTTPCORE-756: Support methods for handling hop-by-hop and connection specific headers. Contributed by Oleg Kalnichevski * HTTPCORE-756: Reject HTTPS requests received over insecure (non-TLS) connection per RFC 9110 section 7.4. Contributed by Oleg Kalnichevski * HTTPCORE-756: Replace CR, LF, NULL in header values with SP per RFC 9110 section 5.5. Contributed by Oleg Kalnichevski * HTTPCORE-759: Add Content-Length to POST, PUT and PATCH with null entity request content (#435). Contributed by Billy <10576351+chrysophylax at users.noreply.github.com> * Improved name/value parsing code. Contributed by Oleg Kalnichevski * ':path' pseudo-Header validation for HTTP/2 Requests (#428). Contributed by Arturo Bernal * HTTPCORE-756: Implement RFC9110 Content-Type validation for OPTIONS requests. Enforce the presence of a valid Content-Type header for OPTIONS requests containing content as per RFC9110 (#424). Contributed by Arturo Bernal * HTTPCORE-756: Enforced non-empty host identifier for http/https URIs. (#423) Contributed by Arturo Bernal * Async requesters to populate request URI authority based on the target host when not explicitly set by the caller. Contributed by Oleg Kalnichevski * Async requesters to support custom / resolved target endpoints Contributed by Oleg Kalnichevski * Replace 'synchronized' blocks with ReentrantLock. (#412) Contributed by Arturo Bernal * HTTPCORE-744: Removed references to URI normalization from URIBuilder. Contributed by Oleg Kalnichevski * Use Timeout#INFINITE instead of Timeout#DISABLED for network operations. Contributed by Oleg Kalnichevski * Add support for Internationalized Domain Names (IDN). Contributed by Arturo Bernal * Added a request interceptor to generate 'Forwarded' HTTP header. Contributed by Arturo Bernal * Use either US-ASCII or UTF-8 encoding for text where appropriate. Contributed by Michael Osipov * Fixed an issue with invalid scoped IPv6 addresses in InetAddressUtils. Contributed by Arturo Bernal * Added a request interceptor to generate 'Via' HTTP header. Contributed by Arturo Bernal Release 5.2.4 ------------------ This is a maintenance release that corrects a major defect discovered since release 5.2.3 that can lead to an H2 connection failing at runtime with the message "Frame size exceeds maximum" when executing requests with an enclosed message body and the remote endpoint having negotiated a maximum frame size larger than the protocol default (16KB). Change Log ------------------- * HTTPCORE-762: H2 protocol handler incorrectly determines the maximum frame size for outgoing frames in case the remote endpoint negotiated a max frame size larger than the protocol default. Contributed by Oleg Kalnichevski Release 5.2.3 ------------------ This is a maintenance release that corrects several defects discovered since release 5.2.2 including a major defect that can cause non-blocking character-based consumers to enter an invalid state and stop processing incoming data. Change Log ------------------- * HTTPCORE-757: AbstractCharDataConsumer fails to correctly handle incomplete UTF8 encoded data split across multiple data packets. Contributed by Oleg Kalnichevski * Support SOCKS5 with username/password authentication Contributed by yndu13 * Non-blocking protocol handler to filter out non-printable ASCII and non-ASCII characters from HTTP headers by default. This makes the behavior of the non-blocking transport consistent with that of the blocking one (#416). Contributed by vismayku <142537063+vismayku at users.noreply.github.com> * HTTPCORE-754: StrictConnPool fails to correctly handle infinite request timeout. Contributed by Oleg Kalnichevski * HTTPCORE-753: Fixed race condition in the IOSession#close method. Contributed by Oleg Kalnichevski Release 5.2.2 ------------------ This is a maintenance release that corrects several defects discovered since release 5.2.1 including a major defect that can cause HTTP/2 connections allocate excessive amount of memory for their output frame buffer if the opposite endpoint transmits a high value of MAX_FRAME_SIZE in its settings. Change Log ------------------- * HTTPCORE-752: I/O reactor fails to initialize socket timeout for TLS connections correctly resulting in infinite (no timeout) by default. Contributed by Oleg Kalnichevski * HTTPCORE-751: H2 protocol handler always resizes the output frame buffer to the remove MAX_FRAME_SIZE instead of doing so only then the remote MAX_FRAME_SIZE is lesser than the current MAX_FRAME_SIZE (partially reverts HTTPCORE-707). Contributed by Oleg Kalnichevski * HTTPCORE-750: Fixed a defect causing AbstractIOSessionPool to create multiple connections under high load at initialization time due to a race condition. Contributed by Oleg Kalnichevski * Handle UnsupportedOperationException in getApplicationProtocol. Contributed by Arturo Bernal * HTTPCORE-742: BasicHttpRequest#setUri does not correctly reset internal state. Contributed by Oleg Kalnichevski * HTTPCORE-733: BasicAsyncEntityProducer sends an extra trailing 0 with UTF-8 encoded content Contributed by Oleg Kalnichevski * Do not duplicate the HttpMessage instance variable slot in subclasses of AbstractMessageWrapper. Contributed by Gary Gregory Release 5.2.1 ------------------ This is a maintenance release that corrects several minor defects discovered since release 5.2 and fixes SOCKS proxy protocol support in the async transport. Change Log ------------------- * HTTPCLIENT-2248: Fixed broken TLS over SOCKS. Contributed by Oleg Kalnichevski * I/O reactor connect process redesign. Contributed by Oleg Kalnichevski * SOCKS protocol handling: Delegate resolution of unknown hostnames to the SOCKS proxy. Contributed by Oleg Kalnichevski * I/O reactor to validate remote endpoint address at the last moment immediately before connecting. Contributed by Oleg Kalnichevski * Bug fix: I/O reactor must handle all runtime exceptions when connecting to remote endpoints. Contributed by Oleg Kalnichevski * Added org.apache.hc.core5.http.protocol.HttpCoreContext#toString(). Contributed by Gary Gregory * Examples should log exceptions instead of swallowing them. Contributed by Gary Gregory * HTTPCORE-729: NPE in ServerHttp1IOEventHandlerFactory due to a missing null check Contributed by Oleg Kalnichevski Release 5.2 ------------------ This is the first GA release in the 5.2 release series. This release finalizes the 5.2 APIs and also corrects a number of defects discovered since the previous release. Please note that 5.2 upgrades the minimal JRE level to version 8 (8u251 is required). Notable changes and features included in the 5.2 series: * Upgrade to Java 8. * Improved support for TLS upgrade and HTTP protocol upgrade (async). * Improved HTTP protocol negotiation. * Improved customization of connection listeners (async). * Improved parsing and formatting of URI components. * Use of Java 8 date / time APIs Change Log ------------------- * Bug fix: HTTP/1.1 protocol handler must continue message processing as long as there is data in the session input buffer. Contributed by Oleg Kalnichevski * HTTPCORE-726: Improved capacity management in SharedInputBuffer. Contributed by John Leacox * Bug fix: Fixed incorrect calculation of the capacity increment in StringAsyncEntityConsumer. Contributed by Oleg Kalnichevski * Add and use TLS accessor methods to use lambdas from httpclient5 later. Contributed by Gary Gregory * Deprecate org.apache.hc.core5.util.LangUtils.equals(Object, Object) in favor or java.util.Objects.equals(Object, Object). Deprecate org.apache.hc.core5.util.LangUtils.equals(Object[], Object[]) in favor or java.util.Arrays.equals(Object[], Object[]). Contributed by Gary Gregory * URI Builder performance optimization (#355) Contributed by jkmcl Release 5.2 BETA2 ------------------ This BETA release corrects a major regression in the TLS handshake handling code introduced in the previous BETA release. This release also includes all fixes from the 5.1 (stable) branch. Please note that 5.2 upgrades the minimal JRE level to version 8 (8u251 is required). Change Log ------------------- * HTTPCORE-713: Optimize InetAddressUtils#isIPv6*Address; check input colon count before performing IPv6 regex validation. Contributed by David Schlosnagle * HTTPCORE-710: In case of some TLS handshake failures (protocol version mismatch) the local TLS engine quietly closes the stream instead of throwing a handshake exception. Contributed by Oleg Kalnichevski * Corrected TLS upgrade support in HttpAsyncRequester. Contributed by Oleg Kalnichevski * Bug fix: Non-blocking TLS sessions fail to update their event interest mask upon TLS handshake initiation. Contributed by Oleg Kalnichevski * Upgrade to RxJava3 Contributed by Ryan Schmitt Release 5.2 BETA1 ------------------ This is the first BETA release in the 5.2 release series that marks the completion of major API changes and starts the transition toward a GA phase. This release also includes all fixes from the 5.1 (stable) branch. Change Log ------------------- * Improved HTTP protocol negotiation. Contributed by Oleg Kalnichevski * Added #clone to BasicHeader. Contributed by Arturo Bernal * Use subclass of ConnectionClosedException to signal request execution failures due to the connection being closed. Requests failed with this exception should generally be safe to re-execute. Contributed by Oleg Kalnichevski * HTTPCORE-698: Migratation to Unit 5. Contributed by Arturo Bernal * HTTPCORE-697: Replaced SimpleDateFormat with Java 8 Time APIs. Contributed by Arturo Bernal * Fixed #format in Deadline; improved #hashCode; replaced SimpleDatteFormat with Java 8 Time APIs. Contributed by Oleg Kalnichevski * Singleton INSTANCE added to RequestConnControl, RequestContent, RequestData, RequestTargetHost and RequestUserAgent. Contributed by Arturo Bernal * HTTPCORE-692: added new rules for H2 header check as per rfc7540 section 8.1.2.2 and 8.1.2.3. Contributed by 风起 * HTTPCORE-691: HttpService and HttpRequestExecutor builders. Contributed by Arturo Bernal * Added URIAuthority and NamedEndpoint setters to URIBuilder (#308). Contributed by Gary Gregory Release 5.2 ALPHA2 ------------------ This is the second ALPHA release in the 5.2 release series that fixes a regression in the TLS layer introduced by the previous ALPHA and adds a number of incremental improvements. Change Log ------------------- * Add PathEntityProducer, an NIO entity provider (#302). Contributed by Gary Gregory * Add SSLContextBuilder NIO Path versions of IO File APIs and re-implement internals with NIO (#301). Contributed by Gary Gregory * Allow setting parameters to null arrays and lists to behave like empty (#300). Contributed by Gary Gregory * Bug fix, regression: TLS handshake result callback does not get called in case of a timeout. Contributed by Oleg Kalnichevski * HTTPCLIENT-2174: URUBuilder to return a new empty list instead of unmodifiable Collections#emptyList. Contributed by Oleg Kalnichevski Release 5.2 ALPHA1 ------------------ This is the first ALPHA release in the 5.2 release series that upgrades minimal JRE level to version 1.8 (8u251 is required) and includes several protocol level and API improvements. It also includes all bug fixes from the 5.1 branch. Change Log ------------------- * Improved Travis CI build Performance. Contributed by Chen Zhang <340355960 at qq.com> * HTTPCORE-682: Custom provider for key manager/trust manager initialization (#296) Contributed by Pawel Veselov * Bug fix: fix data race in StrictConnPool. Contributed by Carter Kozak * Added utility method to add name value pair for uri builder (#288). Contributed by Anurag Agarwal * Use jep244 (ALPN support) back-ported to java 8u251. Contributed by Oleg Kalnichevski * Bug fix: Don't change conn flow control window based on remote SETTINGS. Contributed by Ryan Schmitt * HTTPCLIENT-1916: Add URIBuilder.removeParameter (#283). Contributed by Peter Dettman * Improved parsing and formatting of URI components. Contributed by Oleg Kalnichevski * Let TimeValue/Timeout convert to and from Duration (#263). Contributed by Gary Gregory * Add URIBuilder#getFirstQueryParam(String) to query a parameter by name (#264). Contributed by Gary Gregory * Protocol upgrade APIs redesign. Contributed by Oleg Kalnichevski * TLS upgrade and TLS strategy APIs redesign. Contributed by Oleg Kalnichevski * Added default `ConnectionAcceptor#listen` method that takes an optional attachment as a parameter. Contributed by Oleg Kalnichevski * Upgrade to Java 1.8. Contributed by Oleg Kalnichevski Release 5.1.1 ------------------ This is a maintenance release that corrects a number of defects discovered since release 5.1 including a major defect that can cause a connection pool resource leak. Change Log ------------------- * HTTPCORE-676: Fixed incorrect handling of TLS renegotiation by non-blocking i/o sessions. Contributed by Oleg Kalnichevski * HTTPCORE-616: Make URI authority parsing IPv6 ready. Contributed by Carter Kozak * Improved parsing and formatting of URI components. Contributed by Oleg Kalnichevski * HTTPCORE-673: Fixed incorrect handling of unknown parameters in HTTP/2 SETTINGS frame. Contributed by Oleg Kalnichevski * HTTPCORE-671: URIBuilder does not precent-encode bracketed IPv6 addresses. Contributed by Carter Kozak * HTTPCORE-672: H2ConnPool incorrectly handles validation of closed sessions. Contributed by Oleg Kalnichevski * Fixed race condition when a connection request completes successfully and times out at the same time causing a pool entry leak. Contributed by Oleg Kalnichevski * Fixed TextUtils#toHexString producing incorrect result for negative values. Contributed by Marcono1234 Release 5.1 ----------- This is the first GA release in the 5.1 release series. Notable changes and features included in the 5.1 series: * Conditional conformance with RFC 3986 (Uniform Resource Identifier (URI): Generic Syntax). * Improved support for out of sequence response message handing by the the classic (blocking) HTTP transport. * Improved message builders. Change Log ------------------- * Bug fix: HTTP negotiator factories to accept null TlsStrategy. Contributed by Oleg Kalnichevski * RequestHandlerRegistry to resolve 127.0.0.1 as primary host. Contributed by Oleg Kalnichevski * HTTPCORE-667, HTTPCORE-668, HTTPCORE-670: Added content type and HTTP header constants. Contributed by Arturo Bernal * RFC 3986 conformance: BasicHttpRequest to reject requests whose path component begins with multiple slashes. Contributed by Oleg Kalnichevski * RFC 3986 conformance: BasicHttpRequest to support parsing of valid URI authority components not recognized by java.net.URI. Contributed by Oleg Kalnichevski * Improved message builder support. Contributed by Oleg Kalnichevski * HTTPCORE-666, regression: fixed NPE in ServerHttp1IOEventHandlerFactory. Contributed by Oleg Kalnichevski Release 5.1 BETA3 ------------------ This is likely the last BETA release in the 5.1 release series. The next release is expected to be 5.1 GA. This beta includes a number of new features as well as bug fixes from the stable 5.0.x branch. Notable changes and features included in the 5.1 series: * Conditional conformance with RFC 3986 (Uniform Resource Identifier (URI): Generic Syntax). * Improved support for out of sequence response message handing by the the classic (blocking) HTTP transport. Change Log ------------------- * RFC 3986 conformance: Added `#normalizeSyntax` method to URIBuilder. Contributed by Oleg Kalnichevski * HTTPCORE-663: Added http status code 425. Contributed by Arturo Bernal * HTTPCORE-661: Added http status code 426. Contributed by Arturo Bernal * HTTPCORE-659: Fixed race condition in IOSessionImpl when setting event. Contributed by Nicolas Capponi * HTTPCORE-658: Removed explicit inbound / outbound socket shutdown from the `#close` method of classic HTTP connections. Contributed by Oleg Kalnichevski * HTTPCORE-657: Added TrafficClass to IOReactorConfig (#243). Contributed by Desmond Yeung * Add filters before MAIN_HANDLER so they won't be ignored (#236). Contributed by Rob Spoor * Bug fix: Fixed broken result callback in ProtocolNegotiatorBase. Contributed by Oleg Kalnichevski * Fixed NPE and improve code centralization in URIBuilder (#235). Contributed by Lars Helge Øverland * Added `#appendPath` and `#appendPathSegments` methods to URIBuilder (#234). Contributed by Lars Helge Øverland * Removed connection acceptor API changes (moved to 5.2). Contributed by Oleg Kalnichevski * Removed protocol upgrade API changes (moved to 5.2). Contributed by Oleg Kalnichevski * Bug fix: Do not attempt to shut down non-blocking TLS session gracefully in case the TLS handshake has not been fully completed. Contributed by Oleg Kalnichevski Release 5.1 BETA2 ------------------ This is the second BETA release in the 5.1 release series that includes a number of new features as well as bug fixes from the stable 5.0.x branch. Change Log ------------------- * PR #232: Master try w res and more. - Use try-with-resources. - Use diamonds. - Add missing @Override. - Make better use of Map APIs. - Remove redundant modifiers. - Use Collections.addAll() API instead of loops. - Remote extra semicolons (;). Contributed by Gary Gregory * Bug fix: Fixed ignoring maxResultLength in toString method of EntityUtils. Contributed by Klaw * Bug fix: corrected handling of pushed stream refusal by the HTTP/2 protocol handler. Contributed by Oleg Kalnichevski * Improved connection re-use (keep-alive) tracking by the benchmark; disabled H2 push when running the benchmark with HTTP/2. Contributed by Oleg Kalnichevski * Improved detection of disconnected endpoints by HttpAsyncRequester. Contributed by Oleg Kalnichevski * HTTPCORE-626: in case of an unsuccessful `expect-continue` handshake do not make any attempts to keep the client connection in a consistent state if the server intends to close it on its end. Contributed by Oleg Kalnichevski * Move decision if the i/o session is to be terminated immediately to the protocol handler by default attempt to terminate the i/o session gracefully. Contributed by Oleg Kalnichevski * Optimized request termination by the non-blocking HTTP/1.1 protocol handler in case of a premature response or `expect-continue` handshake failure. Contributed by Oleg Kalnichevski * `AbstractAsyncResponseConsumer` to handle gracefully an inconsistent state caused by the caller signalling a response with no entity but subsequently calling methods to consume data. Contributed by Oleg Kalnichevski * Revised HTTP protocol negotiation for non-blocking I/O sessions Contributed by Oleg Kalnichevski * Revised TLS session initialization in I/O reactor code Contributed by Oleg Kalnichevski * HTTPCORE-653: CancelledKeyException triggers I/O reactor shutdown. Contributed by Oleg Kalnichevski * Make `ReactiveDataConsumer#failed` a no-op if it has already been marked as complete (#227). Contributed by Colin Weld * HTTPCORE-644: non-blocking TLSv1.3 connections can end up in an infinite event spin when closed concurrently by the local and the remote endpoints. Contributed by Oleg Kalnichevski * Calculate pendingOutputRequests before calling produceOutput to avoid race condition. Contributed by Colin Weld Release 5.1 BETA1 ------------------ This is the first BETA release in the 5.1 release series that includes a number of new features as well performance optimizations in the classic HTTP transport. Notable changes and features included in the 5.1 series: * Conditional conformance with RFC 3986 (Uniform Resource Identifier (URI): Generic Syntax). * Improved support for out of sequence response message handing by the the classic (blocking) HTTP transport. Change Log ------------------- * HTTPCORE-649: Implement ByteArrayBuffer.append(ByteBuffer) Contributed by Carter Kozak * Use decimal numbers for endpoint/execution IDs Contributed by Michael Osipov * HTTPCORE-645: Increase blocking default chunk size from 2 KiB to 8 KiB. Contributed by Carter Kozak * HTTPCORE-645: Chunked request streams reuse buffers between requests. Contributed by Carter Kozak * HTTPCORE-639: Add a configurable ResponseOutOfOrder strategy for DefaultBHttpClientConnection. Contributed by Carter Kozak * RFC 3986 conformance: Support percent-encoded reserved characters in the host component. Contributed by Oleg Kalnichevski * RFC 3986 conformance: Revised URI parsing and formatting. Contributed by Oleg Kalnichevski * Better parse and format methods for URIAuthority. Contributed by Oleg Kalnichevski * HTTPCORE-628: Do not encode blanks as + in URI query component. Contributed by Oleg Kalnichevski * Async connection listeners to support passing attachments to endpoints. Contributed by Oleg Kalnichevski Release 5.0.2 ------------------ This release reverts changes to early response handling logic introduced in 5.0.1 and fixes a number of minor defects. Improvement of the early response handling by the classic client protocol handler has been moved to 5.1. Please also note that as of this release HttpCore will use a 3 minute socket timeout by default. Change Log ------------------- * HTTP/1.1 protocol handlers fail to consistently set actual protocol version in the execution context. Contributed by Oleg Kalnichevski * HTTPCORE-648: Buffer array access respects the arrayOffset (#220). Contributed by Carter Kozak * HTTPCORE-646: ChunkedInputStream avoids creating unnecessary buffers. Contributed by Carter Kozak * Revert "Improved handling of early response messages by the classic client protocol handler". Contributed by Oleg Kalnichevski * HTTPCORE-638: SharedOutputBuffer must trigger DataStreamChannel#endStream() once (#204). Contributed by malaysf * Fixed integer overflow in IOWorkers selector (#203). Contributed by Pavel kaplin * HTTPCLIENT-2098: EntityUtils#toByteArray method fails to take into account `maxResultLength` parameter (#202). Contributed by Andriy * Add Automatic-Module-Name to the manifest. Contributed by Niels Basjes * HTTPCORE-636: Logging statements use slf4j interpolation over concatenation (#199). Contributed by Carter Kozak * HTTPCLIENT-2091: Use finite (3 minutes) socket timeout by default. Contributed by Oleg Kalnichevski Release 5.0.1 ------------------ This release improves handling of early response messages by the classic client protocol handler and fixes a number of minor defects. Change Log ------------------- * Add handling of early response messages by the classic client protocol handler. Contributed by Oleg Kalnichevski * HTTPCORE-631: Revised i/o reactor shutdown sequence and resource de-allocation. Contributed by Oleg Kalnichevski * Fix CharArrayBuffer.subSequence(beganIndex,endIndex) to return right result. Contributed by Lee Ray * Added exception callback to async server implementations enabling logging of unexpected and fatal exceptions in the server-side protocol handlers. Contributed by Oleg Kalnichevski Release 5.0 ------------------ This is the first stable (GA) release of HttpCore 5.0. Notable changes and features included in the 5.0 series: * Support for HTTP/2 protocol and conformance to requirements and recommendations of the latest HTTP/2 protocol specification (RFC 7540, RFC 7541) Supported features: ** HPACK header compression ** stream multiplexing (client and server) ** flow control ** response push (client and server) ** message trailers ** expect-continue handshake ** connection validation (ping) ** application-layer protocol negotiation (ALPN) on Java 9+ ** TLS 1.2 security features Features out of scope for 5.0 release: ** padding of outgoing frames ** stream priority ** plain connection HTTP/1.1 upgrade ** CONNECT method * Improved conformance to requirements and recommendations of the latest HTTP/1.1 protocol specification (RFC 7230, RFC 7231) * New asynchronous HTTP transport APIs consistent for both HTTP/1.1 and HTTP/2 transport. * Redesigned I/O reactor APIs and improved NIO based reactor implementation for a greater performance and scalability. * Support for server-side request filters for classic and asynchronous server implementations. Request filters could be used to implement cross-cutting protocol aspects such as the 'expect-continue' handshaking and user authentication / authorization. * Support for Reactive Streams API [http://www.reactive-streams.org/] * Redesigned connection pool implementation with strict connection limit guarantees. The connection pool is expected to have a better performance under higher concurrency due to reduced global pool lock contention. * New connection pool implementation with lax connection limit guarantees and better performance under higher concurrency due to absence of a global pool lock. * Package name space changed to 'org.apache.hc.core5' * Maven group id changed to 'org.apache.httpcomponents.core5' HttpCore 5.0 releases can be co-located with earlier versions. Change Log ------------------- * GitHub #183: IO reactor status equality clean ups. Contributed by Gary Gregory * HTTPCORE-623 GitHub #185: Change org.apache.hc.core5.http.io.BHttpConnection.isDataAvailable() int input to Timeout. Contributed by Gary Gregory * GitHub #188: Add PathEntity, a Path based implementation of HttpEntity. (#188) Contributed by Gary Gregory * GitHub #191: Add org.apache.hc.core5.http.Method.normalizedValueOf(String). Contributed by Gary Gregory * GitHub #193: Reuse constants instead of magic strings. Contributed by Gary Gregory Release 5.0-BETA11 ------------------ This BETA improves handling of illegal or invalid requests on the server-side and fixes a number of defects in HTTP/2 protocol code found since the last release. IMPORTANT: This release is expected to be the last BETA version. If no major design flaws are found, the actual 5.0 API will be frozen and the next version will be promoted to GA. Change Log ------------------- * HTTPCORE-618: org.apache.hc.core5.http.HttpStatus should be a class, not an interface. Contributed by Gary Gregory * HTTPCORE-619: org.apache.hc.core5.reactor.EventMask should be a class, not an interface. Contributed by Gary Gregory * Normalize TimeUnit in TimeValue's #equals() and #hashCode(). Contributed by Michael Osipov * Support for status code 431 (Request Header Fields Too Large) by H2 server-side protocol handler Contributed by Oleg Kalnichevski * Enforce H2 SETTINGS_MAX_HEADER_LIST_SIZE limit for HTTP/2 messages. Contributed by Oleg Kalnichevski * Improved H2 connection window management logic. Contributed by Ryan Schmitt * Use UTF-8 encoding for XML-based content types. Contributed by Michael Osipov * Bug-fix: H2 protocol handler to reset all streams unconditionally in case of an unexpected exception and attempt to close the i/o session gracefully if possible. Contributed by Oleg Kalnichevski * Support for status code 431 (Request Header Fields Too Large). Contributed by Oleg Kalnichevski * Corrected handling of illegal or invalid request heads by async server-side protocol handler Contributed by Oleg Kalnichevski * Bug-fix: Fixed race condition in ReactiveDataConsumer. Contributed by Ryan Schmitt * Bug-fix: Fixed H2 SETTINGS_HEADER_TABLE_SIZE negotiation. Contributed by Ryan Schmitt * Bug-fix: Fixed empty response handling in ReactiveResponseConsumer. Contributed by Ryan Schmitt * Allow for timeout while acquiring lock in StrictConnPool. Contributed by Chris Wildman * Added H2 config option to disable Huffman compression Contributed by Oleg Kalnichevski * HTTPCORE-613: Now allowing 0 for validaterAfterInactivity. Contributed by Peter Frank * HTTPCLIENT-2029: URIBuilder to support parsing of non-UTF8 URIs. Contributed by Oleg Kalnichevski * HTTPCLIENT-2026: Fixed URIBuilder#isOpaque() logic. Contributed by Oleg Kalnichevski * HTTPCORE-611: Minor glitches with TimeValue. Contributed by Gary Gregory Release 5.0-BETA10 ------------------- This BETA fixes a number of defects found since the last release. IMPORTANT: This release is expected to be the last BETA version. If no major design flaws are found, the actual 5.0 API will be frozen and the next version will be promoted to GA. Change Log ------------------- * HTTPCORE-608: Fixes regression that can lead to performance degradation of asynchronous HTTP/1.1 protocol handlers. Contributed by Oleg Kalnichevski * HTTPCORE-606: HTTP/2 protocol handler incorrectly uses larger frame size prior to SETTING handshake completion; revision of SETTINGS handshake implementation. Contributed by Oleg Kalnichevski * Bug fix: HTTP/2 Protocol handler does not update global connection output window in some cases. Contributed by Oleg Kalnichevski * Upgraded Conscrypt dependency to version 2.2.1 Contributed by Oleg Kalnichevski Release 5.0-BETA9 ------------------- This BETA fixes a number of defects found since the last release, improves behavior of the lax (concurrent) connection pools (special thanks to Linton Miller), simplifies and improves input event handling of SSL/TLS sessions and the HTTP/1.1 protocol event handler. Please note that the following interfaces have marked as internal as of this release: IOSession, IOEventHandler, IOEventHandlerFactory. IMPORTANT: This release is expected to be the last BETA version. If no major design flaws are found, the actual 5.0 API will be frozen and the next version will be promoted to GA. Change Log ------------------- * HTTPCORE-604: async HTTP/1.1 protocol handler must close the i/o session if it cannot be kept alive before passing control to the message exchange hanlder. Contributed by Oleg Kalnichevski * Simplification of HTTP/1.1 read event handling logic. Contributed by Oleg Kalnichevski * Redesign of SSL/TLS async I/O event handling. Contributed by Oleg Kalnichevski * HTTPCLIENT-2016 (regression): Tab chars are replaced by question marks in header values. Contributed by Oleg Kalnichevski * Bug fix: Fixed capacity calculation in SessionInputBufferImpl#put. Contributed by Oleg Kalnichevski * Marked IOSession, IOEventHandler and IOEventHandlerFactory interfaces as internal. Contributed by Oleg Kalnichevski * Propagate BasicResponse{Consumer,Producer}#failed() to data{Consumer,Producer}. Contributed by Roy Hashimoto * Regression: Fixed regression in SingleCoreIOReactor leading to unhandled CancelledKeyException and causing abnormal I/O reactor termination. Contributed by Oleg Kalnichevski * HTTPCORE-601: Work-around for SSL session spin on outbound session closure with Conscrypt 2.2.1. Contributed by Oleg Kalnichevski * HTTPCORE-600: SSLIOSession incorrectly disables input interest when there is still decrypted data available in the session input buffer. Contributed by Oleg Kalnichevski * Added statusClass property to StatusLine. Contributed by Behrang <18451+behrangsa at users.noreply.github.com> * HTTPCORE-596: Connection pools to use an optional callback to close out disposed connections. Contributed by Oleg Kalnichevski * Connection pools to close connections gracefully in case of an ordinary connection disposal. Contributed by Oleg Kalnichevski * HTTPCORE-599: I/O sessions to stop reading from the underlying network channel of READ interest is disabled. Contributed by Oleg Kalnichevski * TimeValue to implement Comparable. Contributed by Oleg Kalnichevski * HTTPCORE-597: AsyncEntityProducers#createText always throws IllegalArgumentException. Contributed by Oleg Kalnichevski * HTTPCORE-595: Tolerate NPE thrown by com.android.org.conscrypt.NativeCrypto#SSL_get_shutdown in Android Conscrypt. Contributed by Oleg Kalnichevski * Set accessibility flag on reflected methods in ReflectionUtils. Contributed by Richard Hernandez * Restore original socket timeout after finishing SSL/TLS handshake. Contributed by Richard Hernandez * Add SOAP XML content type. Contributed by Raihaan Shouhell * HTTPCORE-593, HTTPCORE-592, HTTPCORE-590, HTTPCORE-589: LaxConnPool improvements. Contributed by Linton Miller * HTTPCORE-588: Race condition in ComplexCancellable that can lead to operational dependency not being correctly cancelled. Contributed by Oleg Kalnichevski * Execute Socket[Channel]#connect under doPrivileged. Contributed by Simon Willnauer * Fail request execution immediately after submission if the I/O session has already been closed. Contributed by Oleg Kalnichevski * Bug fix: Fixed regression in the HTTP protocol negotiators. Contributed by Oleg Kalnichevski * Bug fix: MessageSupport#formatTokens no longer modifies the array of tokens passed to it as a parameter. Contributed by Oleg Kalnichevski * HTTPCORE-586: Improve LaxConnPool max pool size management. Contributed by Linton Miller * HTTPCORE-585: Fix LaxConnPool#servicePendingRequest to avoid losing pending lease requests it can't fulfill. Contributed by Linton Miller * Possible fix for iteration thread-safety issue. Contributed by Linton Miller * SSL session verifier for classic HTTP requester. Contributed by Oleg Kalnichevski * Bug fix: non-blocking I/O event handler may be null in case of an unexpected exception. Contributed by Oleg Kalnichevski * Bug fix: Corrected immediate termination of non-blocking I/O sessions. Contributed by Oleg Kalnichevski * Bug fix: Ensure consistency of internal buffers in case of I/O or SSL exception. Contributed by Oleg Kalnichevski Release 5.0-BETA8 ------------------- This BETA fixes a number of defects found since the last release and adds several convenience factory and builder classes, mainly for TLS configuration and HTTP message construction. As of this version all server and requester implementations exclude weak TLS protocol versions and ciphers. Please note that the following interfaces have changed: ResponseChannel, AsyncRequestConsumer, AsyncResponseConsumer, HttpContentProducer, SSLServerSetupHandler. IMPORTANT: This release is expected to be the last BETA version. If no major design flaws are found the actual 5.0 API will be frozen and the next version will be promoted to GA. Change Log ------------------- * HTTPCORE-582: Fixed NPE in SingleCoreIOReactor#processEvents Contributed by Oleg Kalnichevski * Redesign of fatal exception handling by the I/O reactors Contributed by Oleg Kalnichevski * HTTPCORE-581: Wrong deadline calculation for non-blocking I/O operations Contributed by Oleg Kalnichevski * Better HttpVersion and ProtocolVersion. (#131) Contributed by Gary Gregory * Classic and async message builders. Contributed by Oleg Kalnichevski * Improved classic and async entity factory methods. Contributed by Oleg Kalnichevski * HTTPCORE-578: Incorrect serialization of HeaderGroup. Contributed by Gary Gregory * HTTPCORE-577: server-side HTTP protocol negotiator to propagate exceptions to the I/O event handler associated with the I/O session. Contributed by Oleg Kalnichevski * HTTPCLIENT-1986: URIBuilder#isPathEmpty method to verify if encodedPath is an empty string. Contributed by Oleg Kalnichevski * SSLIOSession: Improve connectTimeout implementation. Contributed by Ryan Schmitt * HTTPCLIENT-1981: Disallow TRACE requests with an enclosed entity. Contributed by Oleg Kalnichevski * HTTPCLIENT-1978: Filter characters before byte conversion. Contributed by Ryan Schmitt * HTTPCORE-573: FileContentDecoder don't always enforce the maximum number of bytes to transfer. Contributed by Julien Coloos * All server and requester implementation to exclude weak TLS protocol versions and ciphers. Contributed by Oleg Kalnichevski * Improved SSL setup handling for classic requester and server. Contributed by Oleg Kalnichevski * Common TLS support methods migrated from HttpClient. Contributed by Oleg Kalnichevski * Bug fix: non-blocking SSL I/O sessions fail to fire session disconnected event. Contributed by Oleg Kalnichevski * Bug fix: basic entity consumers to clear buffered content when releasing resources. Contributed by Oleg Kalnichevski * conscrypt-openjdk-uber 1.4.1 -> 1.4.2. Contributed by Gary Gregory * Provide more information when a BindException occurs. Contributed by Gary Gregory * Refactor ClassicHttpRequest and ClassicHttpResponse interfaces to extend a new interface HttpEntityContainer. Contributed by Gary Gregory * Update RxJava from 2.2.7 to 2.2.8. Contributed by Gary Gregory Release 5.0-BETA7 ------------------- This BETA release adds support for SOCKS version 5, improves support for TLS handshake timeout configuration, improves URI builder, and fixes various defects. Please note that the following interfaces have changed: TlsStrategy, IOSession, TransportSecurityLayer. Change Log ------------------- * Add a thread-safe capacity channel to AbstractHttp1StreamDuplexer Contributed by Richard Hernandez * HTTPCORE-568: Signal capacity in ReactiveDataConsumer whenever possible (fixes a race condition) Contributed by Richard Hernandez * Made standard HttpEntity implementations immutable / conditionally immutable Contributed by Oleg Kalnichevski * HTTPCLIENT-1968: URIBuilder to split path component into path segments when digesting a URI Contributed by Oleg Kalnichevski * HTTPCLIENT-1968: added utility methods to parse and format URI path segments Contributed by Oleg Kalnichevski * Added SOCKS proxy support to classic (blocking) HTTP requester Contributed by Oleg Kalnichevski * Made AbstractBinAsyncEntityProducer and AbstractCharAsyncEntityProducer conditionally threading-safe Contributed by Oleg Kalnichevski * Added convenience method to test if ContentType instances are of the same MIME type Contributed by Oleg Kalnichevski * Merge connect and handshake timeouts in AbstractIOSessionPool Contributed by Ryan Schmitt * HTTPCLIENT-1960: URIBuilder incorrect handling of multiple leading slashes in path component Contributed by Oleg Kalnichevski * HTTPCLIENT-1959: corrected argument validation in HttpHost constructors; removed deprecated methods Contributed by Oleg Kalnichevski * org.apache.hc.core5.reactor.IOSession: Deprecate lock() in favor of getLock(). Add missing @Override. Contributed by Gary Gregory * HTTPCORE-563: client support for SOCKS version 5 Contributed by David Maplesden * Delay I/O operations on non-blocking SSL sessions until fully initialized Contributed by Oleg Kalnichevski * Add `handshakeTimeout` support throughout Contributed by Ryan Schmitt * Updated the set of project report generated by Maven Contributed by Oleg Kalnichevski * SSLIOSession: Add `connectTimeout` constructor param Contributed by Ryan Schmitt * Update RxJava from 2.1.9 to 2.2.7. Contributed by Gary Gregory Release 5.0-BETA6 ------------------- This BETA release adds support for advanced TLS functions (such as ALPN protocol negotiation) on Java 1.7 and Java 1.8 through Conscrypt TLS library, and fixes a number of defects found since the previous release. Please note AsyncDataConsumer interface has been changed in order to make it simpler to implement. However existing AsyncDataConsumer might require minor modifications. Change Log ------------------- * Removed OSGi module. Contributed by Oleg Kalnichevski * Added support for advanced TLS functions (such as ALPN extension) on Java 1.7 and Java 1.8 through Conscrypt TLS library. Contributed by Oleg Kalnichevski * HTTP/1.1 and HTTP/2 async protocol handlers to use I/O session lock for output synchronization. Contributed by Oleg Kalnichevski * Bug fix: non-blocking HTTP/1.1 server-side streams incorrectly report their keep-alive status. Contributed by Oleg Kalnichevski * Bug fix: fixed a race condition in non-blocking HTTP/1.1 protocol handlers causing premature clearing of output interest. Contributed by Oleg Kalnichevski * Simplified AsyncDataConsumer interface contract Contributed by Oleg Kalnichevski * HTTPCORE-560: Fix LaxConnPool leasing incorrect PoolEntry when processing pending requests Contributed by Desmond Yeung * Deprecate and rename org.apache.hc.core5.util.Timeout.ofMillis(long) to ofMilliseconds(long). Contributed by Gary Gregory * Deprecate and rename org.apache.hc.core5.util.TimeValue.ofMillis(long) to ofMilliseconds(long). Contributed by Gary Gregory * Fix org.apache.hc.core5.util.TimeValue.convert(TimeUnit). Contributed by Gary Gregory * Add org.apache.hc.core5.util.TimeValue.divide(*). Contributed by Gary Gregory * org.apache.hc.core5.http.URIScheme.getId() Contributed by Gary Gregory * HTTPCORE-562: The reason phrase returned by org.apache.hc.core5.http.HttpResponse.getReasonPhrase() may be empty. Contributed by Gary Gregory * TimeValue can parse more leniently: Allow spaces and singular words for time units, for example " 1 SECOND ". Contributed by Gary Gregory * Add HttpStreamResetException.serialVersionUID Contributed by Gary Gregory * Add org.apache.hc.core5.http.HttpRequest.setUri(URI) Contributed by Gary Gregory * org.apache.hc.core5.net.URIBuilder.setHttpHost(HttpHost) Contributed by Gary Gregory * Deprecate constructors in org.apache.hc.core5.http.HttpHost that do not have the scheme argument first in favor of new constructors that do. Contributed by Gary Gregory * Add org.apache.hc.core5.http.HttpHost.create(URI) Contributed by Gary Gregory * Keep the entries in org.apache.hc.core5.http.protocol.UriPatternMatcher#map in insertion order. Contributed by Gary Gregory Release 5.0-BETA5 ------------------- This BETA release fixed a severe regression introduced in 5.0-BETA4. Release 5.0-BETA4 ------------------- This BETA release adds support for Reactive Streams API [http://www.reactive-streams.org/] and fixes compatibility issues with Java 11 new TLS engine as well as a number of defects found since the previous release. This release also includes a redesigned HTTP stress test tool loosely based on Apache Benchmark (AB) command interface with support for HTTP/2. Change Log ------------------- * Resolved compatibility issues with TLS 1.3 engine shipped with Java 11 Contributed by Oleg Kalnichevski * Bug fix: corrected handling of FORCE_HTTP2 version policy by the server-side protocol negotiator Contributed by Oleg Kalnichevski * Redesign of the internal HTTP benchmark loosely based on AB, added support for HTTP/2 stress testing. Contributed by Oleg Kalnichevski * Regression: fixed the response connection control interceptor incorrectly using 'Connection: keep-alive' directive with HTTP/1.0 requests that do not have an explicit 'Connection' request header. Contributed by Oleg Kalnichevski * HTTPCORE-552, HTTPCORE-553, HTTPCORE-554, HTTPCORE-555, HTTPCORE-556, HTTPCORE-557, HTTPCORE-558: added status codes 103, 208, 226, 308, 451, 506, 508, 510 Contributed by sparsick and Georg Berky * HTTPCLIENT-1942: Add support for Reactive Streams Contributed by Ryan Schmitt * Respect CloseMode when closing async TCP sockets Contributed by Ryan Schmitt * Fix typo in message generated by org.apache.hc.core5.io.SocketTimeoutExceptionFactory.toMessage(int). Contributed by Gary Gregory * Update the org.apache.hc.core5.http.protocol.HttpContext.setAttribute(String, Object) API to return the previous value. Contributed by Gary Gregory * Deprecate and rename org.apache.hc.core5.http.EndpointDetails.getSocketTimeout() to getSocketTimeoutMillis(). Contributed by Gary Gregory * Deprecate and rename org.apache.hc.core5.http.MessageHeaders.getAllHeaders() to getHeaders(). Contributed by Gary Gregory * Deprecate and rename org.apache.hc.core5.http.MessageHeaders.getSingleHeaders(String) to getHeader(String). Contributed by Gary Gregory Release 5.0-BETA3 ------------------- This BETA release fixes a number of defects found since the previous release, adds several incremental improvements and improves Javadoc documentation. Change Log ------------------- * Improved information responses (1xx) processing. Contributed by Kirill Usov * Bug fix: fixed AbstractAsyncResponseConsumer to handle null callback parameter Contributed by Oleg Kalnichevski * Bug fix: HTTP stream handlers no longer call #failed event of the associated exchange handler when the message exchange has already been completed. Contributed by Oleg Kalnichevski * Bug fix: fixed NPE caused by null timeout attribute of IOSessionRequest Contributed by Oleg Kalnichevski * Bug fix: fixed incorrect propagation of exception cause in case of an HTTP protocol violation during HTTP protocol version negotiation Contributed by Oleg Kalnichevski * Workaround for misbehaved servers that return HTTP 204 responses with a content Contributed by Alessandro Gherardi * HTTPCORE-510: Avoid an ArithmeticException in AbstractMultiworkerIOReactor by failing earlier by checking ioThreadCount in IOReactorConfig constructor. Contributed by Gary Gregory * HTTPCORE-511: Do not cache result of Runtime.getRuntime().availableProcessors() in IOReactorConfig. Contributed by Gary Gregory * HTTPCORE-509: AVAIL_PROCS is auto-configured based on core count. Contributed by Gary Gregory * HTTPCORE-514: Exceptions defined by HttpCore should clean message strings when built to replace non-printable characters with hex values. Contributed by Gary Gregory * HTTPCORE-517: Allow SecurityManager to stop socket connections. Contributed by Gary Gregory and Paul Thompson * HTTPCORE-537: org.apache.hc.core5.http.message.BasicHttpResponse.toString() prints its code twice and no protocol version. Contributed by Gary Gregory * HTTPCORE-539: Constructing a new FileEntityProducer for a file whose length is greater than 2GB throws an IllegalArgumentException. Contributed by Gary Gregory * HTTPCORE-540: EndpointDetails implements HttpConnectionMetrics. Contributed by Gary Gregory * HTTPCORE-541: Add HttpVersion.ALL for all HTTP versions known to HttpCore. Contributed by Gary Gregory * HTTPCORE-542: Add missing org.apache.hc.core5.http.message.BasicClassicHttpRequest.serialVersionUID Contributed by Gary Gregory * HTTPCORE-544: Add org.apache.hc.core5.http.EndpointDetails.getSocketTimeout() Contributed by Gary Gregory * HTTPCORE-545: Add org.apache.hc.core5.http.message.HeaderGroup.removeHeaders(Header) Contributed by Gary Gregory * HTTPCORE-546: Update org.apache.hc.core5.http.message.HeaderGroup.removeHeader(Header) to return a boolean Contributed by Gary Gregory * HTTPCORE-547: Update org.apache.hc.core5.http.message.HeaderGroup.removeHeaders(String) to return a boolean Contributed by Gary Gregory * HTTPCORE-548: Add missing HttpContext parameter to APIs. Contributed by Gary Gregory * Refactor duplicate messages into a new 0-arg constructor for StreamClosedException. Contributed by Gary Gregory * Refactor timeout APIs to include the actual timeout value. Contributed by Gary Gregory * Refactor timeout APIs to include the scale in the method name; for example 'int getSocketTimeout()' vs. int 'getSocketTimeoutMillis()'. Contributed by Gary Gregory * HTTPCORE-550: When a ParseException is caught and rethrown as an IOException in ChunkDecoder#processFooters(), the IOException does not chain the original ParseException. Contributed by Gary Gregory * Request specific push consumers Contributed by Oleg Kalnichevski * HTTPCLIENT-1927: URLEncodedUtils#parse breaks at double quotes when parsing unquoted values Contributed by Oleg Kalnichevski * HTTPCORE-528: SSL I/O session spins upon abnormal connection closure by the opposite endpoint. Contributed by Oleg Kalnichevski Release 5.0-BETA2 ------------------- This BETA release fixes a number of defects found since the previous release and adds several incremental improvements. Notable changes and features included in the 5.0 series: * Support for HTTP/2 protocol and conformance to requirements and recommendations of the latest HTTP/2 protocol specification (RFC 7540, RFC 7541) Supported features: ** HPACK header compression ** stream multiplexing (client and server) ** flow control ** response push (client and server) ** message trailers ** expect-continue handshake ** connection validation (ping) ** application-layer protocol negotiation (ALPN) on Java 9+ ** TLS 1.2 security features Features out of scope for 5.0 release: ** padding of outgoing frames ** stream priority ** plain connection HTTP/1.1 upgrade ** CONNECT method * Improved conformance to requirements and recommendations of the latest HTTP/1.1 protocol specification (RFC 7230, RFC 7231) * New asynchronous HTTP transport APIs consistent for both HTTP/1.1 and HTTP/2 transport. * Redesigned I/O reactor APIs and improved NIO based reactor implementation for a greater performance and scalability. * Support for server-side request filters for classic and asynchronous server implementations. Request filters could be used to implement cross-cutting protocol aspects such as the 'expect-continue' handshaking and user authentication / authorization. * Redesigned connection pool implementation with strict connection limit guarantees. The connection pool is expected to have a better performance under higher concurrency due to reduced global pool lock contention. * New connection pool implementation with lax connection limit guarantees and better performance under higher concurrency due to absence of a global pool lock. * Package name space changed to 'org.apache.hc.core5' * Maven group id changed to 'org.apache.httpcomponents.core5' HttpCore 5.0 releases can be co-located with earlier versions. The 5.0 APIs are considered feature complete and are not expected to undergo any major changes anymore. The focus of development is now shifting to API polish, code stabilisation and documentation improvements. Change Log ------------------- * HTTP/2 multiplexed requester to support cancellation of individual message exchanges without termination of the underlying I/O session. Contributed by Oleg Kalnichevski * Bug fix: corrected handling of GOAWAY frames by HTTP/2 stream multiplexer Contributed by Oleg Kalnichevski * Bug fix: prevent a tight loop in non-blocking SSL I/O sessions due to a HTTP/2 frame fragment in the SSL input buffer. Contributed by Oleg Kalnichevski * Bug fix: incorrect handing of premature I/O session termination by the server-side application protocol negotiator. Contributed by Oleg Kalnichevski * Strict / lax ALPN handshake mode for HTTP/2 multiplexing requester. Contributed by Oleg Kalnichevski * HTTPCORE-496: Add API org.apache.http.protocol.UriPatternMatcher.entrySet(). Contributed by Gary Gregory * HTTPCORE-499 Make interface Header extend NameValuePair Contributed by Gary Gregory * HTTPCORE-501 org.apache.http.client.utils.URLEncodedUtils.parse() should return a new ArrayList when there are no query parameters. Contributed by Gary Gregory Release 5.0-BETA1 ------------------- This is a major release that renders HttpCore API incompatible with the stable 4.x branch and upgrades HTTP/1.1 and HTTP/2 protocol conformance to the requirements and recommendations of the latest protocol specification. Notable new features in this release: * New HTTP/2 requester optimized for multiplexed execution of requests. Notable changes and features included in the 5.0 series: * Support for HTTP/2 protocol and conformance to requirements and recommendations of the latest HTTP/2 protocol specification (RFC 7540, RFC 7541) Supported features: ** HPACK header compression ** stream multiplexing (client and server) ** flow control ** response push (client and server) ** message trailers ** expect-continue handshake ** connection validation (ping) ** application-layer protocol negotiation (ALPN) on Java 9+ ** TLS 1.2 security features Features out of scope for 5.0 release: ** padding of outgoing frames ** stream priority ** plain connection HTTP/1.1 upgrade ** CONNECT method * Improved conformance to requirements and recommendations of the latest HTTP/1.1 protocol specification (RFC 7230, RFC 7231) * New asynchronous HTTP transport APIs consistent for both HTTP/1.1 and HTTP/2 transport. * Redesigned I/O reactor APIs and improved NIO based reactor implementation for a greater performance and scalability. * Support for server-side request filters for classic and asynchronous server implementations. Request filters could be used to implement cross-cutting protocol aspects such as the 'expect-continue' handshaking and user authentication / authorization. * Redesigned connection pool implementation with strict connection limit guarantees. The connection pool is expected to have a better performance under higher concurrency due to reduced global pool lock contention. * New connection pool implementation with lax connection limit guarantees and better performance under higher concurrency due to absence of a global pool lock. * Package name space changed to 'org.apache.hc.core5' * Maven group id changed to 'org.apache.httpcomponents.core5' HttpCore 5.0 releases can be co-located with earlier versions. The 5.0 APIs are considered feature complete and are not expected to undergo any major changes anymore. The focus of development is now shifting to API polish, code stabilisation and documentation improvements. Change Log ------------------- * HTTP/2 multiplexing requester. Contributed by Oleg Kalnichevski * Compatibility with Java 9 (tested with Oracle JDK 9.0.1). Contributed by Oleg Kalnichevski * Fixed handling of relative request paths in BasicHttpRequest. Contributed by Oleg Kalnichevski * HTTPCORE-494: Add image constants to ContentType. Contributed by Gary Gregory * HTTPCORE-486: set a time limit on processing of pending I/O events by I/O reactors. Contributed by xiaohu-zhang Release 5.0-ALPHA4 ------------------- This is a major release that renders HttpCore API incompatible with the stable 4.x branch and upgrades HTTP/1.1 and HTTP/2 protocol conformance to the requirements and recommendations of the latest protocol specification. Notable changes and features included in the 5.0 series are: * Support for HTTP/2 protocol and conformance to requirements and recommendations of the latest HTTP/2 protocol specification (RFC 7540, RFC 7541) Supported features: ** HPACK header compression ** stream multiplexing (client and server) ** flow control ** response push (client and server) ** message trailers ** expect-continue handshake ** connection validation (ping) ** application-layer protocol negotiation (ALPN) on Java 1.9+ ** TLS 1.2 security features Features out of scope for 5.0 release: ** padding of outgoing frames ** stream priority ** plain connection HTTP/1.1 upgrade ** CONNECT method * Improved conformance to requirements and recommendations of the latest HTTP/1.1 protocol specification (RFC 7230, RFC 7231) * New asynchronous HTTP transport APIs consistent for both HTTP/1.1 and HTTP/2 transport. * Redesigned I/O reactor APIs and improved NIO based reactor implementation for a greater performance and scalability. * Support for server-side request filters for classic and asynchronous server implementations. Request filters could be used to implement cross-cutting protocol aspects such as the 'expect-continue' handshaking and user authentication / authorization. * Redesigned connection pool implementation with strict connection limit guarantees. The connection pool is expected to have a better performance under higher concurrency due to reduced global pool lock contention. * New connection pool implementation with lax connection limit guarantees and better performance under higher concurrency due to absence of a global pool lock. * Package name space changed to 'org.apache.hc.core5' * Maven group id changed to 'org.apache.httpcomponents.core5' HttpCore 5.0 releases can be co-located with earlier versions. Please note that at this point 5.0 APIs are considered API experimental and unstable and are expected to change in the coming releases without providing a migration path. Change Log ------------------- * HTTPCORE-485: Reduced memory footprint of non-blocking SSL sessions by 25%. Contributed by Todor Bonchev <31352118+todorbonchev at users.noreply.github.com> * Fixed propagation of entity details of incoming HTTP/1.1 messages by non-blocking HTTP/1.1 stream duplexer Contributed by Oleg Kalnichevski * Configurable connection pool concurrency policy Contributed by Oleg Kalnichevski * HTTPCORE-390: Connection pool implementation with higher concurrency characteristics and lax total and per route max guarantees. Contributed by Oleg Kalnichevski * Request filters for classic and asynchronous server-side protocol handlers Contributed by Oleg Kalnichevski * Improved handling of 1xx status messages by the classic transport; server expectation (expect-continue) handshake can now be implemented as a cross-cutting aspect by both the classic and asynchronous transports Contributed by Oleg Kalnichevski * Fixed bug in the classic (blocking) HTTP requester causing incorrect release of connections in case a response message has no entity (such as 204) Contributed by Oleg Kalnichevski * HTTPCORE-454: use Timeout class to represent timeout values Contributed by Oleg Kalnichevski * Made HTTP/2 data window update precede sending of the DATA frame to the opposite endpoint. In some extreme circumstances the opposite endpoint can send back a WINDOW_UPDATE frame in-between of these two operation causing the data window value to exceed its maximum valid value Contributed by Oleg Kalnichevski * I/O interest flag in IOSessionImpl requires synchronization instead of atomic operation in order to avoid race condition Contributed by Oleg Kalnichevski * HTTPCORE-472: Fixed problem with blocking message parsers incorrectly throwing "Maximum line length limit exceeded" exception in some corner cases Contributed by Oleg Kalnichevski * Improved I/O reactor APIs with a smaller public API footprint Contributed by Oleg Kalnichevski * Rewrite of I/O reactor internal channel management; more efficient handling of outgoing connection requests Contributed by Oleg Kalnichevski * Fixed delineation of 200 status message in response to CONNECT method Contributed by Oleg Kalnichevski * HTTPCORE-468: Allow HttpAsyncService subclasses to customize the HTTP status code. Contributed by Gary Gregory * HTTPCORE-471: Add APIs URIBuilder.localhost() and setHost(InetAddress) Contributed by Gary Gregory * HTTPCORE-466: Round out the SslContextBuilder by adding missing APIs. Contributed by Gary Gregory Release 5.0-ALPHA3 ------------------- This is a major release that renders HttpCore API incompatible with the stable 4.x branch and upgrades HTTP/1.1 and HTTP/2 protocol conformance to the requirements and recommendations of the latest protocol specification. Notable changes and features included in the 5.0 series are: * Partial support for HTTP/2 protocol and conformance to requirements and recommendations of the latest HTTP/2 protocol specification (RFC 7540, RFC 7541) Supported features: ** HPACK header compression ** stream multiplexing (client and server) ** flow control ** response push (client and server) ** message trailers ** expect-continue handshake ** connection validation (ping) ** application-layer protocol negotiation (ALPN) on Java 1.9+ ** TLS 1.2 security features Unsupported features: ** padding of outgoing frames ** stream priority ** plain connection HTTP/1.1 upgrade ** CONNECT method * Improved conformance to requirements and recommendations of the latest HTTP/1.1 protocol specification (RFC 7230, RFC 7231) * New asynchronous HTTP transport APIs consistent for both HTTP/1.1 and HTTP/2 transport. * Improved HTTP/1.1 and HTTP/2 requester and server implementations. * Redesigned connection pool implementation with reduced pool lock contention. * Plug-in mechanism for HTTP/1.1 protocol switch / upgrade. * Package name space changed to 'org.apache.hc.core5' * Maven group id changed to 'org.apache.httpcomponents.core5' HttpCore 5.0 releases can be co-located with earlier versions. Please note that as of 5.0 HttpCore requires Java 1.7 or newer. Please note that at this point 5.0 APIs are considered API experimental and unstable and are expected to change in the coming releases without providing a migration path. Change Log ------------------- * Support for HTTP protocol version negotiation. Contributed by Oleg Kalnichevski * Support TLS ALPN and disable TLS renegotiation via reflection on Java 1.9+. Contributed by Oleg Kalnichevski * HTTPCORE-452: Add a UriRegexMatcher. Contributed by Gary Gregory * HTTPCORE-453: ServerBootstrap should traverse handler map in insertion order. Contributed by Gary Gregory * HTTPCORE-451: Add a TimeValue class to wrap a long and a TimeUnit. Contributed by Gary Gregory * Fixed HTTP/2 server-side response message delineation when replying to HEAD requests. Contributed by Oleg Kalnichevski * Rewrite of non-blocking HTTP/1.1 connection persistence and re-use code. Contributed by Oleg Kalnichevski * API for graceful shutdown of processes or endpoints Contributed by Oleg Kalnichevski * HTTPCORE-450: Add a Provider parameter in SSLContextBuilder. Contributed by lujianbo <387852424 at qq dot com>, Gary Gregory * Improved handling of premature HTTP/1.1 stream termination by non-blocking protocol handlers. Contributed by Oleg Kalnichevski * Non-blocking connection initializer to support multi-homed remote endpoints. Contributed by Oleg Kalnichevski * HTTPCORE-458: Validate port values Contributed by Gary Gregory Release 5.0-ALPHA2 ------------------- This is a major release that renders HttpCore API incompatible with the stable 4.x branch and upgrades HTTP/1.1 and HTTP/2 protocol conformance to the requirements and recommendations of the latest protocol specification. Notable changes and features included in the 5.0 series are: * Partial support for HTTP/2 protocol and partial conformance to requirements and recommendations of the latest HTTP/2 protocol specification (RFC 7540, RFC 7541) Supported features: ** HPACK header compression ** stream multiplexing (client and server) ** flow control ** response push (client and server) ** message trailers ** expect-continue handshake ** connection validation (ping) ** TLS 1.2 features Unsupported features: ** application-layer protocol negotiation (ALPN) ** padding of outgoing frames ** stream priority ** plain connection HTTP/1.1 upgrade ** CONNECT method ** TLS renegotiation and compression cannot be disabled with Java 1.7 JSSE APIs * Improved conformance to requirements and recommendations of the latest HTTP/1.1 protocol specification (RFC 7230, RFC 7231) * New asynchronous HTTP transport APIs consistent for both HTTP/1.1 and HTTP/2 transport. * Improved HTTP/1.1 and HTTP/2 requester and server implementations. * Redesigned connection pool implementation with reduced pool lock contention. * Support for HTTP/1.1 protocol switch / upgrade. * Package name space changed to 'org.apache.hc.core5' * Maven group id changed to 'org.apache.httpcomponents.core5' HttpCore 5.0 releases can be co-located with earlier versions. Please note that as of 5.0 HttpCore requires Java 1.7 or newer. Please note that at this point 5.0 APIs are considered API experimental and unstable and are expected to change in the coming releases without providing a migration path. Change Log ------------------- * HTTPCORE-424: added ConnPool policy parameter to control connection re-use policy. Contributed by Oleg Kalnichevski * HTTPCORE-413: Minimal chunk size can now be specified as H1Config#chunkSizeHint. The value is treated as a hint. Both classic and NIO attempt to apply it when sending / receiving messages without providing a strict guarantee. Contributed by Oleg Kalnichevski * HTTPCORE-429: NIO connection pool incorrectly reports the number of pending connections per individual route. Contributed by Oleg Kalnichevski * HTTPCORE-432: HTTP/2 support. Contributed by Oleg Kalnichevski * HTTPCORE-418: Add a HttpHost constructor for hostname and scheme. Based on contribution by Joshua Hendrickson * HTTPCORE-436: Port tests from Apache Commons Logging to Apache Log4j 2. Contributed by Gary Gregory * HTTPCORE-434: Corrected handling of HEAD responses without payload headers. Contributed by Oleg Kalnichevski * HTTPCORE-427: SSL I/O sessions to use a finite timeout for SSL shutdown handshake when closing. Contributed by Oleg Kalnichevski * HTTPCORE-422: HttpAsyncResponseConsumer#onEntityEnclosed is triggered for HEAD responses. Contributed by Oleg Kalnichevski * HTTPCORE-420: Blocking HttpServer does not close out persistent connection when shut down. Contributed by Oleg Kalnichevski * HTTPCORE-417: SSLIOSession#writePlain incorrectly handles closed channel condition leading to an infinite loop. Contributed by Oleg Kalnichevski * HTTPCORE-416: DefaultConnectingIOReactor to treat failure to open a channel for a new outgoing connection as a recoverable request failure. Contributed by Oleg Kalnichevski Release 5.0-ALPHA1 ------------------- This is a major release that renders HttpCore API incompatible with the stable 4.x branch and upgrades HTTP/1.1 protocol conformance to the requirements and recommendations of the latest protocol specification. This release lays the foundation for transition to HTTP/2 as the primary transport protocol in the future releases. Notable changes and features included in the 5.0 series are: * Improved conformance to requirements and recommendations of the latest HTTP/1.1 protocol specification (RFC 7230, RFC 7231) * Blocking I/O and NIO HTTP transport implementation has been folded into one module * Package name space changed to 'org.apache.hc.core5' * Maven group id changed to 'org.apache.httpcomponents.core5' HttpCore 5.0 releases can be co-located with earlier versions. Please note that as of 5.0 HttpCore requires Java 1.7 or newer. Please note that at this point 5.0 APIs are considered API experimental and unstable and are expected to change in the coming releases without providing a migration path. Change Log ------------------- * PATCH method support Contributed by Ömer Özkan * HTTPCORE-412: Support for trailing headers in outgoing HTTP messages Based on contribution by Daneel Yaitskov * HTTPCORE-411, RFC 7320, RFC 7321: Made Expect-Continue handshake compliant with the spec Contributed by Oleg Kalnichevski * Fixed handling of pipelined HEAD requests Contributed by Oleg Kalnichevski * HTTPCORE-409: NIO HttpServer does not shutdown listener ExecutorService Contributed by Hiranya Jayathilaka * HTTPCORE-406: PAX-EXAM tests with an embedded HTTP server Contributed by Benson Margulies * RFC 7230: increased the default max number of concurrent connection for the same route from 2 to 5 Contributed by Oleg Kalnichevski * RFC 7230: improved compliance of the default connection re-use strategy Contributed by Oleg Kalnichevski * RFC 7230: reject HTTP/1.1 requests with absent Host header or multiple Host headers Contributed by Oleg Kalnichevski * RFC 7230: permit some empty lines before message head Contributed by Oleg Kalnichevski * RFC 7230: disallow multiple Content-Length headers / header elements Contributed by Oleg Kalnichevski * RFC 7230: reject headers containing whitespaces between the header field name and colon in strict mode (when parsing request messages on the server-side) Contributed by Oleg Kalnichevski * RFC 7230: reject messages with incompatible major protocol version Contributed by Oleg Kalnichevski * HTTP/2: added immutable MessageHead interface common for both HTTP/1.1 and HTTP/2 messages Contributed by Oleg Kalnichevski * HTTPCORE-396: PrivateKeyStrategy does not work with NIO SSL Contributed by Oleg Kalnichevski * RFC 7230: it is legal for any request method to enclose an entity; revised message delineation logic; removed HttpEntityEnclosingRequest interface; fixes HTTPCORE-318, HTTPCORE-380 Contributed by Oleg Kalnichevski * Use CharSequence instead of CharArrayBuffer for header element parsing Contributed by Oleg Kalnichevski * TokenParser to use CharSequence instead of CharArrayBuffer Contributed by Oleg Kalnichevski Release 4.4.4 ------------------- This is a maintenance release that fixes a number of issues discovered since 4.4.3. Please note that as of 4.4 HttpCore requires Java 1.6 or newer. Change Log ------------------- * [HTTPCORE-410] PoolStats made Serializable Contributed by Oleg Kalnichevski * BufferedHttpEntity to use HttpEntity#writeTo when buffering non-repeatable entities Contributed by Oleg Kalnichevski * Fixed race condition in request initialization code in async client protocol handlers Contributed by Oleg Kalnichevski * Fixed handling of pipelined HEAD requests Contributed by Oleg Kalnichevski * [HTTPCORE-409] NIO HttpServer does not shutdown listener ExecutorService Contributed by Hiranya Jayathilaka Release 4.4.3 ------------------- This is a maintenance release that fixes a regression introduced by release 4.4.2. Please note that as of 4.4 HttpCore requires Java 1.6 or newer. Change Log ------------------- * Fixed regression introduced by HTTPCORE-399. Contributed by Oleg Kalnichevski Release 4.4.2 ------------------- This maintenance release fixes a bug in HTTP request pipelining code discovered after 4.4.1 release. Please note that as of 4.4 HttpCore requires Java 1.6 or newer. Change Log ------------------- * Throw ConnectionClosedException instead of IllegalStateException if an attempt is made to use a closed (not bound to a socket) blocking connection. Contributed by Oleg Kalnichevski * [HTTPCORE-399] Non-blocking client connections incorrectly suspend output causing sequential execution of requests. Contributed by Oleg Kalnichevski Release 4.4.1 ------------------- This maintenance release fixes a number of minor bugs found since 4.4. Please note that as of 4.4 HttpCore requires Java 1.6 or newer. Change Log ------------------- * [HTTPCORE-396]: PrivateKeyStrategy does not work with NIO SSL. Contributed by Oleg Kalnichevski * [HTTPCORE-395]: VersionInfo#getUserAgent reports incorrect Java version. Contributed by Michael Osipov * Non-blocking connection should not trigger end-of-stream callback as long as there is still data in the session input buffer. This can cause a series of short pipelined requests to fail prematurely in case of an unexpected connection termination by the opposite endpoint. Contributed by Oleg Kalnichevski Release 4.4 ----------------- This is the first stable (GA) release of HttpCore 4.4. Notable features included in the 4.4 series are: * Support for pipelined request processing on the server-side * Support for pipelined request execution on the client-side * Simplified bootstrapping of blocking and non-blocking (NIO) HTTP server implementations * Inclusion of SSL context initialization utilities from HttpClient Please note that as of 4.4 HttpCore requires Java 1.6 or newer. Change Log ------------------- * Performance optimizations Contributed by Dmitry Potapov * Update Apache Commons Logging version from 1.1.3 to 1.2. Contributed by Gary Gregory Release 4.4-BETA1 ------------------- This is the first BETA release from 4.4 release series. Notable features included in the 4.4 series are: * Support for pipelined request processing on the server-side * Support for pipelined request execution on the client-side * Simplified bootstrapping of blocking and non-blocking (NIO) HTTP server implementations * Inclusion of SSL context initialization utilities from HttpClient * New HTTP element tokenizer implementation Please note that as of 4.4 HttpCore requires Java 1.6 or newer. Release 4.4-ALPHA1 ------------------- This is the first release from the 4.4.x development branch. The most notable features included in this release are: * Support for pipelined request processing on the server-side * Support for pipelined request execution on the client-side * Simplified bootstrapping of blocking and non-blocking (NIO) HTTP server implementations This release also includes all fixes from the stable 4.2.x release branch. Change Log ------------------- * [HTTPASYNC-77] system resources are not correctly deallocated if I/O reactor is shut down while still inactive (not started) Contributed by Oleg Kalnichevski * [HTTPCORE-377] Allow zero SO_LINGER (immediate abortive close). Contributed by Dmitry Potapov * [HTTPCORE-376] AbstractNIOConnPool#requestCancelled() should not process pending requests wnen being shut down. Contributed by Dmitry Potapov * [HTTPASYNC-69]: async request handler is closed by HttpAsyncRequestExecutor#closed if the underlying connection is found to be in an inconsistent state. Contributed by Oleg Kalnichevski * [HTTPCORE-372] Blocking and non-blocking chunk decoders to throw an I/O exception if data stream is terminated without a closing chunk. Contributed by Dmitry Potapov * [HTTPCORE-368] Customizable buffer management strategies for SSLIOSession. Contributed by offbynull * [HTTPCORE-358] Added I/O reactor listener backlog parameter. Contributed by Dmitry Potapov * [HTTPCORE-357] Avoid DNS lookups in SSLIOSession. Contributed by Oleg Kalnichevski * Update JUnit to version 4.11 from 4.9 Contributed by Gary Gregory Release 4.3.2 ------------------- This maintenance release fixes a number of bugs and regressions found since 4.3.1, mostly in the NIO transport components. All users of HttpCore 4.3 are advised to upgrade. Change Log ------------------- * [HTTPCORE-371] Support for SSL re-negotiation with NIO. Contributed by Asankha Perera * [HTTPCORE-373] Out of sequence HTTP response causes NPE in HttpAsyncRequestExecutor. Contributed by Oleg Kalnichevski * [HTTPCORE-370] Race condition if connection request succeeds and times out at the same time. Contributed by Oleg Kalnichevski * (Regression) Fixed synchronization issue in blocking and non-blocking connection pool implementations caused by HTTPCORE-362 Contributed by Oleg Kalnichevski Release 4.3.1 ------------------- This maintenance release fixes a number of bugs and regressions found since 4.3, mostly in the NIO transport components. All users of HttpCore 4.3 are advised to upgrade. Change Log ------------------- * [HTTPCORE-367] (Regression) Non-blocking connections can enter a tight loop while waiting for a chunk header split across multiple TCP frames. Contributed by Oleg Kalnichevski * [HTTPCORE-366] Non-blocking SSLIOSession can enter an infinite loop if the underlying channel receives incoming data simultaneously with inactivity timeout. Contributed by Oleg Kalnichevski * [HTTPCORE-364] IOSessionImpl.getLocalAddress() (etc.) creates unnecessary copy of channel * DefaultConnectingIOReactor / DefaultListeningIOReactor do not correctly apply some initial socket settings. Contributed by Andreas Veithen * [HTTPCORE-357] Avoid DNS lookups in SSLIOSessions in server mode. Contributed by Isaac Cruz Ballesteros * [HTTPCORE-362] Purge pool per route map after closing out expired or idle connections with #closeExpired and #closeIdle methods. Contributed by Oleg Kalnichevski * [HTTPCORE-361] Reduced intermediate garbage in HeaderGroup#getFirstHeader() Contributed by Oleg Kalnichevski * [HTTPCORE-355] HttpAsyncRequestExecutor fails when its handler is not ready to generate a request. Contributed by jd * [HTTPCORE-354] BasicConnFactory don't use SocketConfig#isSoKeepAlive. Contributed by David Ignjic * [HTTPCORE-347] (Regression) HttpResponse#setStatusCode() does not update reason phrase. Contributed by Oleg Kalnichevski Release 4.3 ------------------- This is the first stable (GA) release of HttpCore 4.3. The most notable features in the 4.3 branch are: * Deprecation of preference and configuration API based on HttpParams interface in favor of constructor injection and plain configuration objects. * Reliance on object immutability instead of access synchronization for thread safety. Several old classes whose instances can be shared by multiple request exchanges have been replaced by immutable equivalents. The 4.3 branch also contains performance optimizations such as reduced TCP packet fragmentation and more efficient lease / release operations for pools of persistent connections on the client side. This release also includes all fixes from the 4.2.x release branch. Users of HttpCore 4.2 are encouraged to upgrade. Change Log ------------------- * [HTTPCORE-343] AbstractNIOConnPool to fire request callbacks outside the pool lock. This makes it possible to re-enter pool methods from a callback event. Contributed by Oleg Kalnichevski * [HTTPCORE-340] AbstractNIOConnPool to support lease timeout distinct from connect timeout. Contributed by Ignat Alexeyenko * Blocking connections do not clean session input buffer when closed (input data from a read operation may still remain in the buffer if the connection is re-opened). Contributed by Oleg Kalnichevski Release 4.2.5 ------------------- This maintenance release fixes a number of bugs found in NIO components since 4.2.4. We advise users of HttpCore NIO of all versions to upgrade. This is likely to be the last release in the 4.2.x branch. Change Log ------------------- * [HTTPCORE-345] EntityAsyncContentProducer fails to release resources allocated by the underlying entity when #produceContent is never invoked. Contributed by Tad Whitenight * Non-blocking connection pool to avoid scanning the entire queue of pending connection requests on each connection lease / release operation (under heavy load the request queue can contain a significant number of pending requests, a full linear scan of which can cause massive performance degradation). Contributed by Oleg Kalnichevski * Use bulk ByteBuffer#put method instead of single byte ByteBuffer#put Contributed by Oleg Kalnichevski * [HTTPCORE-336] Unhandled CancelledKeyException leads to a shutdown of the underlying IOReactor. Contributed by Thomas Dudek Release 4.3-BETA2 ------------------- This is the second BETA release from the 4.3.x release branch. This release addresses performance issues in the non-blocking connection pool implementation and also includes a number of performance improvements in the low level NIO based transport components. Change Log ------------------- * [HTTPCORE-300] ContentType to provide support for custom parameters. Contributed by Oleg Kalnichevski * Non-blocking connection pool to avoid scanning the entire queue of pending connection requests on each connection lease / release operation (under heavy load the request queue can contain a significant number of pending requests, a full linear scan of which can cause massive performance degradation). Contributed by Oleg Kalnichevski * Basic connection pool implementations to perform default port resolution for HTTP and HTTPS schemes. Contributed by Oleg Kalnichevski * Use bulk ByteBuffer#put method instead of single byte ByteBuffer#put Contributed by Oleg Kalnichevski * [HTTPCORE-336] Unhandled CancelledKeyException leads to a shutdown of the underlying IOReactor. Contributed by Thomas Dudek Release 4.3-BETA1 ------------------- This is the first BETA release from the 4.3.x release branch. The main theme of the 4.3 release series is streamlining of component configuration and deprecation of the old configuration API based on HttpParams in favor of constructor-based dependency injection and plain objects for configuration parameters. This release also includes performance optimizations intended to reduce TCP packet fragmentation when writing out HTTP messages both in blocking and non-blocking I/O modes, which should result in up to 20% higher throughput for short entity enclosing messages. This release also includes all fixes from the stable 4.2.x release branch. Change Log ------------------- * Reduced TCP packet fragmentation when writing out message content with blocking and non-blocking connections. Contributed by Oleg Kalnichevski * [HTTPCORE-330] Clarify InputStreamEntity length constructor argument. Contributed by John McCarthy * [HTTPCORE-323] Undocumented UnsupportedCharsetException in ContentType#getOrDefault. Contributed by Gary D. Gregory Release 4.2.4 ------------------- This maintenance release fixes a number of bugs found in NIO components since 4.2.3. We advise users of HttpCore NIO of all versions to upgrade. Change Log ------------------- * [HTTPCORE-334] https request to a non-responsive but alive port over a non-blocking connection results in a busy loop in one of I/O dispatch threads. Contributed by Scott Stanton * [HTTPCORE-331] BasicFuture no longer executes notification callbacks inside a synchronized block. Contributed by Oleg Kalnichevski * [HTTPCORE-319] Non-blocking SSLIOSession can fail to shut down correctly when the underlying connection gets terminated abnormally by the opposite endpoint in case there is a truncated or corrupted encrypted content in the input buffer and there is still data in the output buffer that needs to be flushed out (most likely to occur with POST or PUT requests). Contributed by Oleg Kalnichevski Release 4.3-ALPHA1 ------------------- This is the first release from the 4.3.x release branch. The main focus of the 4.3 release series is streamlining of component configuration and deprecation of the old configuration API based on HttpParams in favor of constructor based dependency injection and plain objects for configuration parameters. We are kindly asking all upstream projects to review API changes and help us improve the APIs by providing feedback and sharing ideas on dev@hc.apache.org. This release also includes all fixes from the stable 4.2.x release branch. Release 4.2.3 ------------------- This maintenance release fixes a number of bugs and found since 4.2.2 including a major bug in the NIO module that can cause an infinite loop in SSL sessions under special circumstances when the remote peer terminates the session in the middle of SSL handshake. Please note this issue does not affect blocking I/O components used by HttpClient. We advise users of HttpCore NIO of all versions to upgrade. Change Log ------------------- * [HTTPCORE-319, HTTPCORE-322] Non-blocking SSLIOSession can enter an infinite loop under special circumstances when the remote peer terminates the session in the middle of SSL handshake. Contributed by Paul Donohue and Oleg Kalnichevski * [HTTPCORE-316] HeaderGroup#clone removes headers from the original object. Contributed by Markus Thies * [HTTPCORE-315] AbstractNIOConnPool fails to correctly deallocate connection if it is immediately released from the session request callback causing a resource leak. Contributed by Scott Stanton * [HTTPCORE-313] ContentType#parse now ignores empty and blank charset attributes. HttpEntityUtils#toString now throws checked I/O exception if it encounters an unsupported charset. Contributed by Oleg Kalnichevski Release 4.2.2 ------------------- This is a maintenance release that fixes a number of bugs and regressions found since 4.2.1 including a major bug in the NIO module causing incorrect handling of outgoing Content-Length delimited messages larger than 2GB. Users of HttpCore 4.2 are advised to upgrade. Change Log ------------------- * [HTTPCORE-312] NIO length delimited content encoder incorrectly handles messages larger than 2GB. Contributed by Oleg Kalnichevski * [HTTPCORE-310] Fixed regression in DefaultConnectionReuseStrategy causing it to incorrectly flag connections as non-reusable after a 204, 205 or 304 response. Contributed by Oleg Kalnichevski * [HTTPCORE-309] Fixed regression in HttpAsyncRequestExecutor causing it to handle 204, 205 and 304 responses incorrectly by returning a message with an enclosed content body. Contributed by Oleg Kalnichevski * [HTTPCORE-306] I/O reactor TCP_NODELAY parameter now defaults to true. Contributed by Oleg Kalnichevski * [HTTPASYNC-21] request execution handler does not get closed in case of a premature HTTP exchange termination. Contributed by Oleg Kalnichevski * [HTTPCORE-303] ContentType made Serializable. Contributed by Oleg Kalnichevski Release 4.2.1 ------------------- This is a maintenance release that fixes a non-critical number of bugs found since 4.2. Users of HttpCore 4.2 are encouraged to upgrade. Change Log ------------------- * [HTTPCORE-299] ContentType should rely on Charset#name() instead of Charset#toString() for building header value. Contributed by Oleg Kalnichevski * [HTTPCORE-263] Under rare circumstances incorrectly delineated or garbled HTTP message can cause an IndexOutOfBoundsException in AbstractSessionInputBuffer#readLine() Contributed by Michael Pujos * Made connection pools use FIFO algorithm instead of LIFO when leasing / releasing persistent connections. Contributed by Oleg Kalnichevski * [HTTPCORE-298] Fixed non-blocking SSLIOSession state can getting out of sync with the underlying IOSession in case the I/O session is terminated by the I/O reactor rather than by the protocol handler. Contributed by Sandeep Tamhankar * Fixed NPE in StringEntity constructor thrown if ContentType#getCharset is null. Contributed by Oleg Kalnichevski Release 4.2 ------------------- This is the first stable (GA) release of HttpCore 4.2. The most notable features included in this release are connection pool components for blocking and non-blocking HTTP connections and new asynchronous client and server-side protocol handlers. New protocol handling API used in conjunction with connection pooling components is expected to make development of asynchronous HTTP client agents and HTTP proxies easier and less error prone. Connection pool components are based on mature code migrated from HttpClient and HttpAsyncClient modules but have a slightly different API that makes a better use of Java standard concurrent primitives. Change Log ------------------- * Fixed HttpAsyncRequestExecutor incorrect execution of message exchanges that span across multiple hosts (for instance, in case of a request redirect). Contributed by Oleg Kalnichevski * AbstractHttpClientConnection#isResponseAvailable method now catches SocketTimeoutException and returns false. Contributed by Oleg Kalnichevski * [HTTPCORE-296] server-side connections (both blocking and non-blocking) can now handle entity enclosing requests without Content-Length and Transfer-Encoding headers. Contributed by Oleg Kalnichevski * [HTTPCORE-295] [HTTPCORE-288] Provided direct access to the underlying socket of non-blocking HTTP connection to allow modification of socket level settings such as TCP_NODELAY, SO_KEEPALIVE, TrafficClass, etc. Contributed by Oleg Kalnichevski * [HTTPCORE-289] HttpAsyncService fails to invoke Cancellable#cancel() when the ongoing HTTP exchange is aborted by the client. Contributed by Oleg Kalnichevski Incompatible changes -------------------- [Compared to release version 4.1.4] The following methods have been deprecated for some time now and have been deleted: org.apache.http.impl.SocketHttpServerConnection#createHttpDataReceiver(Socket, int, HttpParams) org.apache.http.impl.SocketHttpServerConnection#createHttpDataTransmitter(Socket, int, HttpParams) org.apache.http.protocol.HttpRequestHandlerRegistry#matchUriRequestPattern(String, String) The following classes have been deprecated for some while now and have been deleted: org.apache.http.nio.entity.ByteArrayNIOEntity org.apache.http.nio.entity.FileNIOEntity org.apache.http.nio.entity.HttpNIOEntity org.apache.http.nio.entity.StringNIOEntity org.apache.http.nio.protocol.NHttpClientHandlerBase org.apache.http.nio.protocol.NHttpServiceHandlerBase Release 4.2-BETA1 ------------------- This is the first BETA release of HttpCore 4.2. This release comes with completely redesigned and rewritten asynchronous protocol handlers. New protocol handling API used in conjunction with connection pooling components is expected to make development of asynchronous HTTP client agents and HTTP proxies easier and less error prone. Sample application shipped with the release include an example of an HTTP file server capable of direct channel (zero copy) data transfer and an example of a non-blocking, fully streaming reverse proxy. This release also incorporates bug fixes from the stable 4.1.x branch and includes an updated HttpCore tutorial. Release 4.1.4 ------------------- This is a maintenance release that fixes a number of bugs found since 4.1.3. It is also likely to be the last release in the 4.1.x branch. Change Log ------------------- * [HTTPCORE-286] Canceled I/O session can cause an IllegalStateException in BaseIOReactor#validate leading to an abnormal termination of the I/O reactor. Contributed by Oleg Kalnichevski * [HTTPCORE-257] Fixed incorrect results produced by DefaultConnectionReuseStrategy when handling response messages whose content entity has been decoded or modified by a protocol interceptor. Contributed by Oleg Kalnichevski * [HTTPCORE-283] Workaround for a bug causing termination of the I/O reactor in case of exception thrown by NHttpServiceHandler#requestReceived or NHttpClientHandler#responseReceived methods. A more comprehensive fix for the bug applied to the 4.2 branch. Contributed by Oleg Kalnichevski * [HTTPCORE-281] ResponseConnControl protocol interceptor does not correctly populate connection persistence control headers when sending a HTTP/1.1 response message in response to a HTTP/1.0 request message. Contributed by William R. Speirs * [HTTPCORE-282] The default value of the internal event mask of newly created non-blocking I/O is not correctly initialized, which causes the READ interest bit to get cleared in the interest op queuing mode unless the event mask is explicitly reset. Contributed by Sadeep Jayasumana and Oleg Kalnichevski * [HTTPCORE-268] Handle runtime exceptions thrown by SSLEngine. Contributed by Oleg Kalnichevski Release 4.2-ALPHA2 ------------------- This is the second ALPHA release of HttpCore 4.2. This release comes with completely redesigned and rewritten asynchronous protocol handlers. New protocol handling API used in conjunction with connection pooling components introduced in the previous ALPHA release is expected to make development of asynchronous HTTP client agents and HTTP proxies easier and less error prone. Sample application shipped with the release include an example of an HTTP file server capable of direct channel (zero copy) data transfer and an example of a non-blocking, fully streaming reverse proxy. We are kindly asking existing and prospective users of HttpCore to review and try out the new protocol handlers and give us feedback while the 4.2 API is still not final. If no major flaws are discovered the 4.2 API is expected to be frozen with the next BETA release. Please note that new features included in this release are still considered experimental and their API may change in the future ALPHA releases. This release also marks the end of support for Java 1.3. As of this release HttpCore requires Java 1.5 for all its components. Several classes and methods deprecated between versions 4.0-beta1 and 4.0 GA (more than two years ago) have been removed in this release. Change Log ------------------- * [HTTPCORE-270] Fixed IllegalStateException in AbstractSessionOutputBuffer and AbstractSessionInputBuffer. Contributed by William R. Speirs * [HTTPCORE-269] Connection pools incorrectly handle lease requests when the max limit for the given route has been exceeded and all connections in the route pool are stateful. Contributed by Oleg Kalnichevski Release 4.2-ALPHA1 ------------------- This is the first ALPHA release of 4.2. The most notable feature included in this release is support for connection pools of blocking and non-blocking HTTP connections. Connection pool components are based on mature code migrated from HttpClient and HttpAsyncClient modules but have a slightly different API that makes a better use of Java standard concurrent primitives. Support for connection pools in HttpCore is expected to make development of client and proxy HTTP services easier and less error prone. Please note that new features included in this release are still considered experimental and their API may change in the future ALPHA releases. This release also marks the end of support for Java 1.3. As of this release HttpCore requires Java 1.5 for all its components. Several classes and methods deprecated between versions 4.0-beta1 and 4.0 GA (more than two years ago) have been removed in this release. Change Log ------------------- * [HTTPCORE-268] Handle runtime exceptions thrown by SSLEngine. Contributed by Oleg Kalnichevski Release 4.1.3 ------------------- This is an emergency release that fixes a severe regression in the non-blocking SSL I/O code introduced in release 4.1.2. * [HTTPCORE-266] SSLIOSession does not correctly terminate if the opposite end shuts down connection without sending a 'close notify' message causing an infinite loop in the I/O dispatch thread. Contributed by Oleg Kalnichevski Release 4.1.2 ------------------- This is a patch release that fixes a number of bugs found in the previous version. Please note that several classes and methods deprecated between versions 4.0-beta1 and 4.0 GA (more than two years ago) will also be removed in the 4.2 branch. Users of 4.0.x versions are advised to upgrade and replace deprecated API calls following recommendations in Javadocs. * [HTTPCORE-261] IOSession#setSocketTimeout() method does not reset the timeout count. Contributed by Oleg Kalnichevski * [HTTPCORE-260] Non-blocking SSL I/O session can terminate prematurely causing message body truncation when message content is chunk coded and the connection is closed on the opposite end. Contributed by Oleg Kalnichevski * [HTTPCORE-257] Fixed incorrect results produced by DefaultConnectionReuseStrategy when handling response messages whose content entity has been decoded or modified by a protocol interceptor. Contributed by Oleg Kalnichevski Release 4.1.1 ------------------- This is a patch release that fixes a number of non-critical issues found since release 4.1. This release marks the end of support for Java 1.3. As of release 4.2 HttpCore will require Java 1.5 for all its components. Please note that several classes and methods deprecated between versions 4.0-beta1 and 4.0 GA (more than two years ago) will also be removed in the 4.2 branch. Users of 4.0.x versions are advised to upgrade and replace deprecated API calls following recommendations in Javadocs. * In case of an unexpected end of stream condition (the peer closed connection prematurely) truncated Content-Length delimited message bodies will cause an I/O exception. Application can still choose to catch and ignore ConnectionClosedException in order to accept partial message content. Contributed by Oleg Kalnichevski * [HTTPCORE-255]: Fixed resource management in InputStreamEntity#writeTo() Contributed by Oleg Kalnichevski * [HTTPCORE-254]: Erratic results from metrics (sebb) * [HTTPCORE-242]: Fixed NPE in AsyncNHttpClientHandler caused by an early response to an entity enclosing request. Contributed by Oleg Kalnichevski Release 4.1 ------------------- This is the first stable (GA) release of HttpCore 4.1. This release provides a compatibility mode with JREs that have a naive (broken) implementation of SelectionKey API and also improves compatibility with the Google Android platform. There has also been a number of performance related improvements and bug fixes in both blocking and non-blocking components. Change Log ------------------- * [HTTPCORE-240]: DefaultConnectingIOReactor leaks a socket descriptor if the session request fails. Contributed by Oleg Kalnichevski * [HTTPCORE-239]: The ChunkEncoder could request for a negative buffer limit causing an IllegalArgumentException. Contributed by Asankha Perera * [HTTPCORE-236]: SSLIOSession#isAppInputReady() does not check the status of the session input buffer. Contributed by Dmitry Lukyanov * [HTTPCORE-233]: EntityUtils#toString() and EntityUtils#toByteArray() to return null if HttpEntity#getContent() is null Contributed by Oleg Kalnichevski * [HTTPCORE-231] Fixed incorrect handling of HTTP entities by non-blocking LengthDelimitedDecoder when the Content-Length value is larger than Integer.MAX_VALUE. Contributed by Oleg Kalnichevski Release 4.1-BETA2 ------------------- This is the second BETA release of HttpCore 4.1. This is mainly a bug fix release that addresses a number of non-critical bugs. The most significant change in this release is deprecation of the HttpEntity#consumeContent() method and streamlining of connection management and resource deallocation by HTTP entities. Please refer to the Javadocs for details. * [HTTPCORE-229] AbstractSessionInputBuffer#readLine(CharArrayBuffer) returns incorrect number of characters read by the method when using non-standard HTTP element charset. Contributed by Oleg Kalnichevski * Non-blocking connections can trigger #responseReady / #requestReady events by mistake when the underlying session is already closed. Contributed by Oleg Kalnichevski * [HTTPCORE-228] Fixed NPE in AsyncNHttpServiceHandler caused by entity enclosing requests if no matching request handler can be found. Contributed by Oleg Kalnichevski * [HTTPCORE-227] Fixed incorrect request / response count by non-blocking connections. Contributed by Harold Lee * [HTTPCORE-226] Improved compatibility of NIO components with Google Android. Contributed by Oleg Kalnichevski * ByteArrayBuffer, CharArrayBuffer, BasicHeader, BufferedHeader, HeaderGroup, BasicRequestLine, BasicStatusLine made Serializable. Contributed by Oleg Kalnichevski Release 4.1-BETA1 ------------------- This is the first BETA release of HttpCore 4.1. This release finalizes the API introduced in the 4.1 development branch. It also fixes a number of bugs discovered since the previous release and delivers a number of performance optimizations in the blocking HTTP transport components. The blocking HTTP transport is expected to be 5% to 10% faster compared to previous releases. * [HTTPCORE-222] Fixed Import-Package in the OSGi META-INF Contributed by Willem Jiang * [HTTPCORE-177] Reduce intermediate data buffering by reading large chunks of data directly from the underlying socket stream. This results in improved performance of blocking read operations. Contributed by Oleg Kalnichevski * [HTTPCORE-220] IdentityDecoder fails to detect end of stream when using file channels. Contributed by Asankha C. Perera * [HTTPCORE-218] ChunkEncoder#write method no longer returns incorrect value if the data to write is greater than the size of the internal buffer used by the encoder. Contributed by Richie Jefts * [HTTPCORE-209] Added parameter to set SO_REUSEADDR on sockets bound to a local address. Contributed by Oleg Kalnichevski * [HTTPCORE-207] SocketHttp*Connection classes can leak sockets if the connection is half-closed Contributed by David Koski Release 4.1-ALPHA1 ------------------- This is the first public release from the 4.1 branch of HttpCore. This release adds a number of new features, most notable being introduction of compatibility mode with IBM JREs and other JREs with naive (broken) implementation of SelectionKey API. Please note new classes and methods added in the 4.1 branch are still considered API unstable. * Ensure that an attempt is made to close out all active sessions gracefully in case of an abnormal shutdown of the I/O reactor. Contributed by Oleg Kalnichevski * [HTTPCORE-201] OSGi Export-Package to specify release version Contributed by Oleg Kalnichevski * [HTTPCORE-183] Added Thread-safe implementations of HttpParams and HttpProcessor - SyncBasicHttpParams and ImmutableHttpProcessor classes Contributed by Oleg Kalnichevski * [HTTPCORE-199] ContentInputStream implements InputStream#available(). Contributed by Oleg Kalnichevski * [HTTPCORE-195] Truncated chunk-coded streams can now be tolerated by catching and discarding TruncatedChunkException. Contributed by Oleg Kalnichevski * [HTTPCORE-155] Compatibility mode with IBM JRE and other JREs with naive (broken) implementation of SelectionKey. Contributed by Marc Beyerle and Oleg Kalnichevski * [HTTPCORE-191] Blocking HTTP connections are now capable of correctly preserving their internal state on SocketTimeoutExceptions, which makes it possible to continue reading from the connection after a socket timeout. Contributed by Oleg Kalnichevski * [HTTPCORE-190] ChunkedInputStream is now capable of correctly preserving its internal state on SocketTimeoutExceptions, which makes it possible to continue reading from the stream after a socket timeout. Contributed by Oleg Kalnichevski Release 4.0.1 ------------------- This is a patch release addressing a number of issues discovered since the 4.0 release. Users of NIO module are advised to upgrade. * [HTTPCORE-198] CONNECT request includes Host header for HTTP 1.1 connections. Contributed by Oleg Kalnichevski * [HTTPCORE-196] SSLIOSession now unwraps encrypted data more aggressively eliminating long pauses when receiving data over non-blocking connections. Contributed by Oleg Kalnichevski * [HTTPCORE-197] Fixed bug causing the non-blocking ChunkDecoder to report some data stream as truncated under special conditions. Contributed by Denis Rogov and Oleg Kalnichevski * SSLIOSession#isAppOutputReady and SSLIOSession#isAppInputReady no longer ignore the application event mask causing I/O event notifications for unrequested type of events. Contributed by Oleg Kalnichevski * [HTTPCORE-193] Fixed problem with SSLIOSession incorrectly handling of end-of-stream condition. Contributed by Asankha C. Perera and Oleg Kalnichevski Release 4.0 ------------------- This is the first stable (GA) release of HttpCore 4.0. This release mainly improves the documentation and fixes a few minor bugs reported since the previous release. HttpCore now comes with a complete tutorial presenting an in-depth coverage of the API. HttpCore is a set of low level HTTP transport components that can be used to build custom client and server-side HTTP services with a minimal footprint. HttpCore supports two I/O models: blocking I/O model based on the classic Java I/O and non-blocking, event driven I/O model based on Java NIO. The blocking I/O model may be more appropriate for data intensive, low latency scenarios, whereas the non-blocking model may be more appropriate for high latency scenarios where raw data throughput is less important than the ability to handle thousands of simultaneous HTTP connections in a resource efficient manner. * [HTTPCORE-180] Fixed NPE in standard I/O event dispatchers when IOEventDispatch#disconnected fires before the session was fully initialized (IOEventDispatch#connected was not called). Contributed by Oleg Kalnichevski * [HTTPCORE-175] Chunk decoders no longer accept truncated chunks as valid input. Contributed by Oleg Kalnichevski Release 4.0 Beta 3 ------------------- The third BETA version of HttpComponents Core has been released. This is a maintenance release, which addresses a number of issues discovered since the previous release. The only significant new feature is an addition of an OSGi compliant bundle combining HttpCore and HttpCore NIO jars. * [HTTPCORE-173] Tolerate missing closing chunk if the chunk coded content is terminated by the end of stream (EOF) condition. Contributed by Oleg Kalnichevski * [HTTPCORE-174] Position is incremented twice in ContentLengthInputStream#skip(long) Contributed by Ildar Safarov * [HTTPCORE-125] OSGi bundle containing HttpCore & HttpCore NIO jars. Contributed by Oleg Kalnichevski * CancelledKeyException thrown in BaseIOReactor#validate() no longer causes a premature I/O reactor shutdown. Contributed by Oleg Kalnichevski * [HTTPCORE-172] Added #close() method to SharedInputBuffer and SharedOutputBuffer. The purpose of the new method is to close the buffer in case of normal / orderly termination of the underlying HTTP connection. Use #shutdown() method to force-close the buffer in case of abnormal / exceptional termination of the underlying connection. Contributed by Oleg Kalnichevski * [HTTPCORE-170] Fixed race condition in SharedOutputBuffer. Contributed by Jason Walton * [HTTPCORE-169] Fixed bug causing connecting I/O reactors to shut down due to ClosedChannelException if a pending session request is cancelled before the new channel has been registered with the selector. Contributed by Anders Wallgren * [HTTPCORE-167] Fixed handling the end of stream (EOF) condition in the #isStale() check of blocking HTTP connections. Contributed by Oleg Kalnichevski * [HTTPCORE-166] NIO reactors now maintain an audit log of fatal exceptions, which can be used to examine the cause and problems experienced during the shutdown process. Contributed by Oleg Kalnichevski * [HTTPCORE-165] Improved handling of CancelledKeyException in I/O reactors Contributed by Oleg Kalnichevski Release 4.0 Beta 2 ------------------- The second BETA version of HttpComponents Core has been released. This release adds a number of improvements to the NIO components, most notable being improved asynchronous client-side and server-side protocol handlers. There has been a number of important bug fixes in HttpCore NIO module, whereas HttpCore base module has had very few changes. All upstream projects dependent on HttpCore NIO are strongly advised to upgrade. * [HTTPCORE-163] Fixed AbstractMultiworkerIOReactor#execute() to correctly propagate the original I/O exception in case of an abnormal termination. Contributed by Patrick Moore * Changed behavior of IdentityDecoder & LengthDelimitedDecoder to throw an IOException if data is attempted to be written beyond the length of a FileChannel. Previously would write nothing. Contributed by Sam Berlin * Fixed bug in LengthDelimitedDecoder & IdentityDecoder that caused transfers to a FileChannel to overwrite arbitrary parts of the file, if data was buffered in SessionInputBuffer. Contributed by Sam Berlin * Fixed concurrency bug in the ThrottlingHttpServerHandler protocol handler. Contributed by Oleg Kalnichevski * Fixed bug in SharedInputBuffer that caused input events to be incorrectly suspended. Contributed by Asankha C. Perera * [HTTPCORE-150] Entity implementation that serializes a Java object Contributed by Andrea Selva * [HTTPCORE-157] ChunkedOutputStream#flush() now behaves consistently with the specification of OutputStream#flush(). Contributed by Oleg Kalnichevski * [HTTPCORE-147] Fixed handling of requests with partially consumed content in ThrottlingHttpServiceHandler. Contributed by Oleg Kalnichevski * [HTTPCORE-92] ChunkEncoder splits input data larger than available space in the session output buffer into smaller chunks instead of expanding the buffer. Contributed by Andrea Selva and Oleg Kalnichevski * [HTTPCORE-149] I/O reactors now count period of inactivity since the time of the last read or write operation. Previously only read operations resulted in timeout counter reset. Contributed by Oleg Kalnichevski * [HTTPCORE-148] Improved asynchronous server and client HTTP protocol handler implementations. Contributed by Sam Berlin * [HTTPCORE-143] Ensure the underlying channel is closed if the session request is canceled or times out. Contributed by Oleg Kalnichevski * [HTTPCORE-140] Fixed timeout handling in ThrottlingHttpServiceHandler. Contributed by Lorenzo Moretti and Oleg Kalnichevski Release 4.0 Beta 1 ------------------- The first BETA version of HttpComponents Core has been released. This release can be considered a major milestone, as it marks the end of API instability in HttpCore. As of this release the API compatibility between minor releases in 4.x codeline will be maintained. This release includes several major improvements such as enhanced HTTP message parsing API and optimized parser implementations, Java 5.0 compatibility for HttpCore NIO extensions. Upstream projects are strongly encouraged to upgrade to the latest release. The focus of the development efforts will be gradually shifting towards providing better test coverage, documentation and performance optimizations. Change Log: ---------- * [HTTPCORE-141] Session request timeout in DefaultConnectingIOReactor invalidates the request. Contributed by Oleg Kalnichevski * [HTTPCORE-137] DefaultHttpRequestFactory extended to support all methods specified in RFC 2616 (except CONNECT). Contributed by Oleg Kalnichevski * Replaced HTTP parameter linking with a simple child/parent stack. Contributed by Oleg Kalnichevski * [HTTPCORE-134] all serialVersionUID attributes are private Contributed by Roland Weber * [HTTPCORE-133] Clone support for basic HTTP message elements and non-streaming entities. Contributed by Oleg Kalnichevski * [HTTPCORE-127] Improved API for lifecycle management of listening I/O reactors. One can now suspend and resume listener endpoints. Contributed by Asankha C. Perera * [HTTPCORE-112] DefaultConnectionReuseStrategy interprets token sequences Contributed by Roland Weber * [HTTPCORE-122] new interface TokenIterator and basic implementation Contributed by Roland Weber * HttpCore NIOSSL classes moved to HttpCore NIO. Contributed by Oleg Kalnichevski * HttpCore NIO ported to Java 1.5. Contributed by Oleg Kalnichevski * [HTTPCORE-130] Fixed over-synchronization bug leading to a thread deadlock condition in SSL IOEventDispatch implementations. Contributed by Oleg Kalnichevski * [HTTPCORE-37] HttpParams beans Contributed by Stojce Dimski * [HTTPCORE-128] Simplified injection of custom NIO connection implementations. Contributed by Oleg Kalnichevski * [HTTPCORE-126] Improved HTTP message parsing API and optimized parser implementations. Contributed by Oleg Kalnichevski * Do not include "Connection: close" to 500 responses per default. Contributed by Oleg Kalnichevski * [HTTPCORE-121] new interface HeaderElementIterator Contributed by Andrea Selva * [HTTPCORE-123] Fixed problem with SSLSession losing buffered data, if the connection has been closed by the peer. Contributed by Risto Reinpõld Release 4.0 Alpha 6 ------------------- The sixth ALPHA version of HttpComponents Core has been released. This release sports an improved message parsing and formatting API in the base module and lots of incremental improvements and bug fixes in the NIO and NIOSSL modules. Based on the improved API, it is now possible to send and receive SIP messages with HttpComponents Core. HttpCore is now feature complete and we are planning to freeze the public APIs as of next release (BETA1). * [HTTPCORE-120] new interface HeaderIterator, available from HttpMessage Contributed by Roland Weber * [HTTPCORE-118] Purge closed sessions prior to opening new ones. This should reduce chances of running out of memory when opening and closing lots of NIO connections in a tight loop. Contributed by Oleg Kalnichevski * [HTTPCORE-117] Fixed bug preventing protocol handlers from closing timed out NIO connection when pending output (output session buffer is not empty). Contributed by Oleg Kalnichevski * [HTTPCORE-86] Allow for optional handling of runtime exceptions thrown by protocol handlers to ensure the I/O dispatch thread remains running. Contributed by Oleg Kalnichevski * [HTTPCORE-116] moved parameter names to interfaces Contributed by Roland Weber * [HTTPCORE-109] Improved shutdown process of the I/O reactors in NIO modules. I/O reactors now attempt to terminate connections gracefully before shutting down the underlying socket channels. Contributed by Oleg Kalnichevski * [HTTPCORE-107] allow sending and receiving of SIP messages Contributed by Roland Weber * [HTTPCORE-114]: Fixed incorrect handling of the end-of-stream condition in SSLIOSession. Oleg Kalnichevski * [HTTPCORE-110] refactored message parsing and formatting logic Contributed by Roland Weber * [HTTPCORE-113] Removed unnecessary target hostname resolution from non-blocking client protocol handlers. Oleg Kalnichevski * [HTTPCORE-108] Close all channels registered with the I/O reactor during shutdown. Fixed the problem with DefaultListeningIOReactor not releasing socket ports until JVM is restarted. Oleg Kalnichevski * [HTTPCORE-106] Pluggable HTTP message parsers and message writers Oleg Kalnichevski * [HTTPCORE-105] Consistent class names in base and NIO modules Oleg Kalnichevski * [HTTPCORE-100] revised HttpContext hierarchy Contributed by Roland Weber * [HTTPCORE-103] NIO connections now attempt to consume all available session data while parsing HTTP messages. This can potentially improve performance of non-blocking SSL connections. Contributed by Steffen Pingel * [HTTPCORE-102] Exceeding of maximum line length limit detected late Contributed by Steffen Pingel * [HTTPCORE-21] Transport and connection metrics Contributed by Andrea Selva and Oleg Kalnichevski * [HTTPCORE-91] new interceptor RequestDate, renamed constants in protocol.HTTP Contributed by Roland Weber * [HTTPCORE-90] Version detection based on build-time properties Contributed by Oleg Kalnichevski and Roland Weber * [HTTPCORE-88] Added convenience methods to HttpRequestInterceptorList, HttpResponseInterceptorList interfaces Contributed by Andrea Selva * [HTTPCORE-89]: Fixed bug in DefaultConnectingIOReactor causing incorrect handling of local (immediate) connections on some platforms (affects Sun Solaris 2.9 / Sparc and likely other Solaris 2.x platforms) Contributed by Sam Berlin Release 4.0 Alpha 5 ------------------- The fifth ALPHA version of HttpComponents Core has been released. This release delivers a number of incremental improvements across the board in all modules and adds several performance oriented features such as ability to transfer data directly between a file and a socket channels. HttpCore is almost fully feature complete now and we are likely to freeze the public APIs as of next release (BETA1). * [HTTPCORE-87] RuntimeExcpetions thrown in I/O worker threads are now correctly propagated to the I/O reactor. Contributed by Oleg Kalnichevski * [HTTPCORE-84]: Removed DateUtils/DateParseException from core. Contributed by Roland Weber * [HTTPCORE-63]: Made I/O select interval configurable for all default I/O reactor implementations. Contributed by Oleg Kalnichevski and Anders Wallgren * [HTTPCORE-82]: Revised linking of HttpParams to reduce potential for misuse. Method #setDefaults() removed from the HttpParams interface. Contributed by Roland Weber * [HTTPCORE-81]: Maximum line length and maximum header counts parameters are now correctly enforced in both base and NIO modules. Fixed maximum line length check when parsing folded header lines. Contributed by Steffen Pingel * Added HTTP client handler implementation that allocates fixed size content buffers upon initialization and is capable of throttling the rate of I/O events in order to make sure those content buffers do not get overflown. Contributed by Oleg Kalnichevski * [HTTPCORE-76]: Added IOSession#shutdown() method intended to force-close I/O sessions (primarily needed to terminate hung SSL connections). Contributed by Sandeep Tamhankar * [HTTPCORE-78]: Added ByteBufferAllocator interface that can be used to apply different ByteArray allocation strategies to session and content buffers. Use heap bound implementation for short-lived or variable in length (requiring frequent content re-allocation) buffers. Contributed by Steffen Pingel * [HTTPCORE-77]: The result of CharsetDecoder#decode() and CharsetEncoder#encode() was not checked for errors resulting in an infinite loop in SessionInputBuffer#readLine() and SessionOutputBuffer#writeLine() when malformed characters were processed. Contributed by Steffen Pingel * [HTTPCORE-71]: HttpParams can be copied. Contributed by Roland Weber * [HTTPCORE-75]: DefaultNHttpServerConnection and DefaultNHttpClientConnection now correctly terminate the underlying I/O session when closed. BufferingHttpServiceHandler now correctly applies connection keep-alive strategy when sending a response with no content body. Contributed by Steffen Pingel * [HTTPCORE-73]: Fixed bug preventing NHttpServiceHandler#responseReady and NHttpClientHandler#requestReady events from being fired if the HTTP message has no content body. Contributed by Steffen Pingel * [HTTPCORE-67]: Improved event listener interface Contributed by Oleg Kalnichevski * [HTTPCORE-43]: Support for FileChannel#transferFrom() and FileChannel#transferTo() methods. Direct coping from and to FileChannel is expected to improve performance of file bound operations Contributed by Andrea Selva * [HTTPCORE-66]: Fixed handling of HTTP HEAD methods Contributed by Steffen Pingel and Oleg Kalnichevski * [HTTPCORE-58]: NIO HTTP connections changed to throw checked ConnectionClosedException instead of unchecked IllegalStateException when an attempt is made to perform an I/O operation on a closed conection Contributed by Oleg Kalnichevski * [HTTPCORE-56]: DefaultConnectingIOReactor no longer terminates due to a CancelledKeyException, if a session request gets canceled before selection key is fully initialized. Contributed by Oleg Kalnichevski Release 4.0 Alpha 4 ------------------- The forth ALPHA version of HttpComponents Core has been released. The ALPHA4 release fixes a number of bugs and adds a number of improvements to HttpCore base and HttpCore NIO extensions. HttpCore NIO can be used to build HTTP services intended to handle thousands of simultaneous connections with a small number of I/O threads. This release also introduces NIOSSL extensions that can be used to extend HttpCore non-blocking transport components with ability to transparently encrypt data in transit using SSL/TLS protocol. * [HTTPCORE-49]: DefaultConnectingIOReactor can now correctly handle unresolved socket addresses. It no longer terminates with the UnresolvedAddressException runtime exception. Contributed by Oleg Kalnichevski * [HTTPCORE-42]: Added server-side API for the expectation verification. Improved support for the 'expect: continue' handshake in HttpCore and HttpCore NIO. Contributed by Oleg Kalnichevski * [HTTPCORE-26]: Added SSL support for HttpCore NIO. Contributed by Oleg Kalnichevski * [HTTPCORE-40]: API classes no longer reference impl classes in module-main. Contributed by Roland Weber * [HTTPCORE-39]: Refactored HttpStatus, spun off [English]ReasonPhraseFactory. Contributed by Roland Weber * [HTTPCORE-32]: HttpRequestInterceptorList, HttpResponseInterceptorList Contributed by Roland Weber * [HTTPCORE-38]: Packages nio.impl.* are now impl.nio.*, same for examples. Contributed by Roland Weber * [HTTPCORE-27]: I/O reactors can now accept a thread factory as an optional parameter. Contributed by Oleg Kalnichevski * [HTTPCORE-36]: Fixed #setHandlers() method and matching of request URIs with a query part in HttpRequestHandlerRegistry Contributed by Oleg Kalnichevski * [HTTPCORE-28]: DefaultConnectingIOReactor now maintains a queue of connect requests and registers new sessions with the selector on the I/O thread. Contributed by Oleg Kalnichevski * [HTTPCORE-29] DefaultConnectingIOReactor changed to ensure IOExceptions are correctly propagated to the caller, if an exception is thrown while initializing a newly connected socket. Contributed by Oleg Kalnichevski * [HTTPCORE-24] Fixed bug in non-blocking connection implementations, which prevented the session buffer from being correctly flushed when the content coding process has been completed. Contributed by Oleg Kalnichevski * [HTTPCORE-23] Fixed threading bug in DefaultConnectingIOReactor. Contributed by Asankha C. Perera Release 4.0 Alpha 3 ------------------- The third ALPHA version of HttpCore has been released. The ALPHA3 release includes a number of API optimizations and improvements and introduces a set of NIO extensions to the HttpCore API. NIO extensions can be used to build HTTP services intended to handle thousands of simultaneous connections with a small number of I/O threads. * [HTTPCORE-15] Provided a interafce to access IP address of the local and remote end points. Contributed by Oleg Kalnichevski * [ HTTPCORE-14] Scheme, SocketFactory and SecureSocketFactory moved to HttpClient. Decoupled HttpHost and Scheme. Contributed by Oleg Kalnichevski * [HTTPCORE-13] Refactored HttpProcessor interface and related impl classes Contributed by Roland Weber * [HTTPCORE-11] Client connection interface no longer defines a specific method to open a connection. HTTP connections can now represent any abstract I/O transport such as those based on NIO API. Contributed by Oleg Kalnichevski * [HTTPCORE-10] Non-blocking (async) client-side I/O transport based on NIO. Contributed by Oleg Kalnichevski * [HTTPCORE-9] Non-blocking (async) server-side I/O transport based on NIO. Contributed by Oleg Kalnichevski * [HTTPCORE-7] ConnectionReuseStrategy interface changed to allow access to the HTTP execution context. Contributed by Roland Weber * [HTTPCORE-6] Header implementation allowing for performance short-cuts when serializing and deserializing HTTP headers. Contributed by Oleg Kalnichevski * [HTTPCORE-5] Header, HeaderElement, NameValuePair, RequestLine, StatusLine, HttpVersion changed to interfaces. API no longer contains any parsing and formatting code and does not imply any specific physical representation of HTTP messages and their elements. Contributed by Oleg Kalnichevski Release 4.0 Alpha 2 ------------------- This is a maintenance release that mostly fixes minor problems found since the previous release. The upstream projects are strongly encouraged use this release as a dependency while HttpCore undergoes another round of reviews and optimization in the SVN trunk Change Log: --------- * [HTTPCORE-4] optional header and line length limits to contain OOME risks Contributed by Oleg Kalnichevski Release 4.0 Alpha 1 ------------------- This release represents a complete redesign of the Jakarta Commons HttpClient 3.x API and a significant rewrite of the core HTTP components derived from HttpClient 3.0 code base. These components will form the foundation of the future releases of Jakarta HttpClient and can also be used separately to build custom client- and server-side HTTP services. SECURITY.md0100644 0000000 0000000 00000002126 00000250600 011355 0ustar000000000 0000000 # HttpComponents Security The HttpComponents Security process and model are on our website's [Security](https://hc.apache.org/security.html) page. If you believe you found a vulnerability in Apache HttpClient, please contact the [Apache Security Team](https://www.apache.org/security/). doap_HttpComponents_Core.rdf0100644 0000000 0000000 00000011336 00000250600 015224 0ustar000000000 0000000 2007-11-15 Apache HttpComponents Core HTTP transport library including support for asynchronous execution based on Java NIO. HttpCore is a set of low level HTTP transport components that can be used to build custom client and server side HTTP services with a minimal footprint. HttpCore supports two I/O models: blocking I/O model based on the classic Java I/O and non-blocking, event driven I/O model based on Java NIO. Java Hypertext Transfer Protocol Version 2 (HTTP/2) IETF RFC 7540 HPACK: Header Compression for HTTP/2 IETF RFC 7541 Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing IETF RFC 7230 Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content IETF RFC 7231 Hypertext Transfer Protocol -- HTTP/1.0 IETF RFC 1945 mvnw0100644 0000000 0000000 00000024653 00000250600 010507 0ustar000000000 0000000 #!/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. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Apache Maven Wrapper startup batch script, version 3.3.2 # # Optional ENV vars # ----------------- # JAVA_HOME - location of a JDK home dir, required when download maven via java source # MVNW_REPOURL - repo url base for downloading maven distribution # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output # ---------------------------------------------------------------------------- set -euf [ "${MVNW_VERBOSE-}" != debug ] || set -x # OS specific support. native_path() { printf %s\\n "$1"; } case "$(uname)" in CYGWIN* | MINGW*) [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" native_path() { cygpath --path --windows "$1"; } ;; esac # set JAVACMD and JAVACCMD set_java_home() { # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched 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" JAVACCMD="$JAVA_HOME/jre/sh/javac" else JAVACMD="$JAVA_HOME/bin/java" JAVACCMD="$JAVA_HOME/bin/javac" if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 return 1 fi fi else JAVACMD="$( 'set' +e 'unset' -f command 2>/dev/null 'command' -v java )" || : JAVACCMD="$( 'set' +e 'unset' -f command 2>/dev/null 'command' -v javac )" || : if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 return 1 fi fi } # hash string like Java String::hashCode hash_string() { str="${1:-}" h=0 while [ -n "$str" ]; do char="${str%"${str#?}"}" h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) str="${str#?}" done printf %x\\n $h } verbose() { :; } [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } die() { printf %s\\n "$1" >&2 exit 1 } trim() { # MWRAPPER-139: # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. # Needed for removing poorly interpreted newline sequences when running in more # exotic environments such as mingw bash on Windows. printf "%s" "${1}" | tr -d '[:space:]' } # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties while IFS="=" read -r key value; do case "${key-}" in distributionUrl) distributionUrl=$(trim "${value-}") ;; distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; esac done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" case "${distributionUrl##*/}" in maven-mvnd-*bin.*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; :Linux*x86_64*) distributionPlatform=linux-amd64 ;; *) echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 distributionPlatform=linux-amd64 ;; esac distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" ;; maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; esac # apply MVNW_REPOURL and calculate MAVEN_HOME # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" distributionUrlName="${distributionUrl##*/}" distributionUrlNameMain="${distributionUrlName%.*}" distributionUrlNameMain="${distributionUrlNameMain%-bin}" MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" exec_maven() { unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" } if [ -d "$MAVEN_HOME" ]; then verbose "found existing MAVEN_HOME at $MAVEN_HOME" exec_maven "$@" fi case "${distributionUrl-}" in *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; esac # prepare tmp dir if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } trap clean HUP INT TERM EXIT else die "cannot create temp dir" fi mkdir -p -- "${MAVEN_HOME%/*}" # Download and Install Apache Maven verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." verbose "Downloading from: $distributionUrl" verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" # select .zip or .tar.gz if ! command -v unzip >/dev/null; then distributionUrl="${distributionUrl%.zip}.tar.gz" distributionUrlName="${distributionUrl##*/}" fi # verbose opt __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v # normalize http auth case "${MVNW_PASSWORD:+has-password}" in '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; esac if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then verbose "Found wget ... using wget" wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then verbose "Found curl ... using curl" curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" elif set_java_home; then verbose "Falling back to use Java to download" javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" cat >"$javaSource" <<-END public class Downloader extends java.net.Authenticator { protected java.net.PasswordAuthentication getPasswordAuthentication() { return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); } public static void main( String[] args ) throws Exception { setDefault( new Downloader() ); java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); } } END # For Cygwin/MinGW, switch paths to Windows format before running javac and java verbose " - Compiling Downloader.java ..." "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" verbose " - Running Downloader.java ..." "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" fi # If specified, validate the SHA-256 sum of the Maven distribution zip file if [ -n "${distributionSha256Sum-}" ]; then distributionSha256Result=false if [ "$MVN_CMD" = mvnd.sh ]; then echo "Checksum validation is not supported for maven-mvnd." >&2 echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 exit 1 elif command -v sha256sum >/dev/null; then if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then distributionSha256Result=true fi elif command -v shasum >/dev/null; then if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then distributionSha256Result=true fi else echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 exit 1 fi if [ $distributionSha256Result = false ]; then echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 exit 1 fi fi # unzip and move if command -v unzip >/dev/null; then unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" else tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" fi printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" clean || : exec_maven "$@" mvnw.cmd0100644 0000000 0000000 00000015400 00000250600 011237 0ustar000000000 0000000 <# : batch portion @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @REM distributed with this work for additional information @REM regarding copyright ownership. The ASF licenses this file @REM to you under the Apache License, Version 2.0 (the @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM @REM http://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @REM KIND, either express or implied. See the License for the @REM specific language governing permissions and limitations @REM under the License. @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- @REM Apache Maven Wrapper startup batch script, version 3.3.2 @REM @REM Optional ENV vars @REM MVNW_REPOURL - repo url base for downloading maven distribution @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output @REM ---------------------------------------------------------------------------- @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) @SET __MVNW_CMD__= @SET __MVNW_ERROR__= @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% @SET PSModulePath= @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) ) @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% @SET __MVNW_PSMODULEP_SAVE= @SET __MVNW_ARG0_NAME__= @SET MVNW_USERNAME= @SET MVNW_PASSWORD= @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) @echo Cannot start maven from wrapper >&2 && exit /b 1 @GOTO :EOF : end batch / begin powershell #> $ErrorActionPreference = "Stop" if ($env:MVNW_VERBOSE -eq "true") { $VerbosePreference = "Continue" } # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl if (!$distributionUrl) { Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" } switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { "maven-mvnd-*" { $USE_MVND = $true $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" $MVN_CMD = "mvnd.cmd" break } default { $USE_MVND = $false $MVN_CMD = $script -replace '^mvnw','mvn' break } } # apply MVNW_REPOURL and calculate MAVEN_HOME # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ if ($env:MVNW_REPOURL) { $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" } $distributionUrlName = $distributionUrl -replace '^.*/','' $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" if ($env:MAVEN_USER_HOME) { $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" } $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" exit $? } if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" } # prepare tmp dir $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null trap { if ($TMP_DOWNLOAD_DIR.Exists) { try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } } } New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null # Download and Install Apache Maven Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." Write-Verbose "Downloading from: $distributionUrl" Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" $webclient = New-Object System.Net.WebClient if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) } [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null # If specified, validate the SHA-256 sum of the Maven distribution zip file $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum if ($distributionSha256Sum) { if ($USE_MVND) { Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." } Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." } } # unzip and move Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null try { Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null } catch { if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { Write-Error "fail to move MAVEN_HOME" } } finally { try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } } Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" pom.xml0100644 0000000 0000000 00000034777 00000250600 011122 0ustar000000000 0000000 org.apache.httpcomponents httpcomponents-parent 14 4.0.0 org.apache.httpcomponents.core5 httpcore5-parent Apache HttpComponents Core 5.4.3 Apache HttpComponents Core is a library of components for building HTTP enabled services https://hc.apache.org/httpcomponents-core-5.4.x/${project.version}/ 2005 pom Jira https://issues.apache.org/jira/browse/HTTPCORE scm:git:https://gitbox.apache.org/repos/asf/httpcomponents-core.git scm:git:https://gitbox.apache.org/repos/asf/httpcomponents-core.git https://github.com/apache/httpcomponents-core/tree/${project.scm.tag} 5.4.3 apache.website Apache HttpComponents Website scm:svn:https://svn.apache.org/repos/asf/httpcomponents/site/components/httpcomponents-core-5.4.x/LATEST/ httpcore5 httpcore5-h2 httpcore5-reactive httpcore5-testing 1.8 1.8 true 2.5.2 5.13.3 3.0 5.0.0 4.11.0 1.7.36 2.25.0 3.1.10 1.21.3 5.4 1.37 javax.net.ssl.SSLEngine,javax.net.ssl.SSLParameters,java.nio.ByteBuffer,java.nio.CharBuffer,jdk.net.ExtendedSocketOptions,jdk.net.Sockets org.apache.httpcomponents.core5 httpcore5 ${project.version} org.apache.httpcomponents.core5 httpcore5-h2 ${project.version} org.apache.httpcomponents.core5 httpcore5-reactive ${project.version} org.apache.httpcomponents.core5 httpcore5-testing ${project.version} org.apache.httpcomponents.core5 httpcore5 tests ${project.version} org.conscrypt conscrypt-openjdk-uber ${conscrypt.version} org.junit junit-bom ${junit.version} pom import org.hamcrest hamcrest ${hamcrest.version} test org.mockito mockito-core ${mockito.version} test org.slf4j slf4j-api ${slf4j.version} org.apache.logging.log4j log4j-slf4j-impl ${log4j.version} org.apache.logging.log4j log4j-core ${log4j.version} org.testcontainers testcontainers ${testcontainers.version} org.testcontainers junit-jupiter ${testcontainers.version} org.openjdk.jmh jmh-core ${jmh.version} test org.openjdk.jmh jmh-generator-annprocess ${jmh.version} test clean verify maven-jar-plugin ${Automatic-Module-Name} ${project.url} maven-javadoc-plugin ${project.url}/httpcore5/apidocs/ ${project.url}/httpcore5-h2/apidocs/ org.apache.maven.plugins maven-checkstyle-plugin validate-main validate hc-stylecheck/default.xml hc-stylecheck/asl2.header true true false ${basedir}/src/main ${basedir}/src/test checkstyle com.github.siom79.japicmp japicmp-maven-plugin 0.25.4 ${project.groupId} ${project.artifactId} ${api.comparison.version} jar true true METHOD_NEW_DEFAULT true true @org.apache.hc.core5.annotation.Internal org.apache.hc.core5.testing.reactive.ReactiveTestUtils true false false verify cmp org.apache.rat apache-rat-plugin verify check **/.checkstyle **/.pmd **/*.iml **/.externalToolBuilders/** maven-eclipse.xml src/docbkx/resources/** src/test/resources/*.truststore src/test/resources/*.p12 **/.dockerignore bin/** maven-project-info-reports-plugin false index dependency-info dependency-management issue-management licenses mailing-lists scm summary maven-javadoc-plugin true ${project.url}/httpcore5/apidocs/ ${project.url}/httpcore5-h2/apidocs/ true Apache HttpCore HTTP/1.1 org.apache.hc.core5* Apache HttpCore HTTP/2 org.apache.hc.core5.http2* Apache HttpCore Reactive Streams Bindings org.apache.hc.core5.reactive* Apache HttpCore Testing org.apache.hc.core5.testing*:org.apache.hc.core5.benchmark* javadoc com.github.siom79.japicmp japicmp-maven-plugin cmp-report ${project.groupId} ${project.artifactId} ${api.comparison.version} jar ${project.groupId} ${project.artifactId} ${project.version} jar @org.apache.hc.core5.annotation.Internal org.apache.hc.core5.testing.reactive.ReactiveTestUtils true httpcore5/0040755 0000000 0000000 00000000000 00000250600 011503 5ustar000000000 0000000 httpcore5/pom.xml0100644 0000000 0000000 00000007552 00000250600 013026 0ustar000000000 0000000 4.0.0 org.apache.httpcomponents.core5 httpcore5-parent 5.4.3 httpcore5 Apache HttpComponents Core HTTP/1.1 2005 Apache HttpComponents HTTP/1.1 core components org.apache.httpcomponents.core5.httpcore5 org.junit.jupiter junit-jupiter test org.hamcrest hamcrest test org.mockito mockito-core test org.slf4j slf4j-api test org.apache.logging.log4j log4j-slf4j-impl test org.apache.logging.log4j log4j-core test src/main/resources true **/*.properties org.apache.maven.plugins maven-jar-plugin test-jar maven-project-info-reports-plugin false index dependencies dependency-info summary httpcore5/src/0040755 0000000 0000000 00000000000 00000250600 012272 5ustar000000000 0000000 httpcore5/src/main/0040755 0000000 0000000 00000000000 00000250600 013216 5ustar000000000 0000000 httpcore5/src/main/java/0040755 0000000 0000000 00000000000 00000250600 014137 5ustar000000000 0000000 httpcore5/src/main/java/org/0040755 0000000 0000000 00000000000 00000250600 014726 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/0040755 0000000 0000000 00000000000 00000250600 016147 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/0040755 0000000 0000000 00000000000 00000250600 016541 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/0040755 0000000 0000000 00000000000 00000250600 017556 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/annotation/0040755 0000000 0000000 00000000000 00000250600 021730 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/annotation/Contract.java0100644 0000000 0000000 00000003573 00000250600 024355 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Defines behavioral contract enforced at runtime by instances of annotated classes. */ @Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS) public @interface Contract { /** * Gets the threading behavior for annotated type. *

* The default value is {@link ThreadingBehavior#UNSAFE}. *

* * @return the threading behavior for annotated type. */ ThreadingBehavior threading() default ThreadingBehavior.UNSAFE; } httpcore5/src/main/java/org/apache/hc/core5/annotation/Experimental.java0100644 0000000 0000000 00000003205 00000250600 025225 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * The field or method to which this annotation is applied is marked as experimental. */ @Documented @Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD}) @Retention(RetentionPolicy.CLASS) public @interface Experimental { } httpcore5/src/main/java/org/apache/hc/core5/annotation/Internal.java0100644 0000000 0000000 00000003304 00000250600 024344 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * The package, class or method to which this annotation is applied is marked as internal * and its use ought to be avoided. */ @Documented @Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PACKAGE}) @Retention(RetentionPolicy.CLASS) public @interface Internal { } httpcore5/src/main/java/org/apache/hc/core5/annotation/Obsolete.java0100644 0000000 0000000 00000003322 00000250600 024344 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * The field or method to which this annotation is applied is marked as implementing * requirements of the HTTP protocol or a related protocol that are now obsolete. */ @Documented @Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.CLASS) public @interface Obsolete { } httpcore5/src/main/java/org/apache/hc/core5/annotation/ThreadingBehavior.java0100644 0000000 0000000 00000004427 00000250600 026164 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.annotation; /** Defines types of threading behavior enforced at runtime. */ public enum ThreadingBehavior { /** * Instances of classes with the given contract are expected to be fully immutable * and thread-safe. */ IMMUTABLE, /** * Instances of classes with the given contract are expected to be immutable if their * dependencies injected at construction time are immutable and are expected to be thread-safe * if their dependencies are thread-safe. */ IMMUTABLE_CONDITIONAL, /** * Instances of classes with the given contract are expected to maintain no state * and to be thread-safe. */ STATELESS, /** * Instances of classes with the given contract are expected to be fully thread-safe. */ SAFE, /** * Instances of classes with the given contract are expected to be thread-safe if their * dependencies injected at construction time are thread-safe. */ SAFE_CONDITIONAL, /** * Instances of classes with the given contract are expected to be non thread-safe. */ UNSAFE } httpcore5/src/main/java/org/apache/hc/core5/annotation/package-info.java0100644 0000000 0000000 00000002412 00000250600 025113 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Provides annotations for public interface definitions */ package org.apache.hc.core5.annotation; httpcore5/src/main/java/org/apache/hc/core5/concurrent/0040755 0000000 0000000 00000000000 00000250600 021740 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/concurrent/BasicFuture.java0100644 0000000 0000000 00000013545 00000250600 025024 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.concurrent; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TimeoutValueException; /** * Basic implementation of the {@link Future} interface. {@code BasicFuture} * can be put into a completed state by invoking any of the following methods: * {@link #cancel()}, {@link #failed(Exception)}, or {@link #completed(Object)}. * * @param the future result type of an asynchronous operation. * @since 4.2 */ public class BasicFuture implements Future, Cancellable { private final FutureCallback callback; private volatile boolean completed; private volatile boolean cancelled; private volatile T result; private volatile Exception ex; private final ReentrantLock lock; private final Condition condition; /** * Constructs a new instance for a FutureCallback. * * @param callback the FutureCallback, may be {@code null}. */ public BasicFuture(final FutureCallback callback) { super(); this.callback = callback; this.lock = new ReentrantLock(); this.condition = lock.newCondition(); } @Override public boolean isCancelled() { return this.cancelled; } @Override public boolean isDone() { return this.completed; } private T getResult() throws ExecutionException { if (this.ex != null) { throw new ExecutionException(this.ex); } if (cancelled) { throw new CancellationException(); } return this.result; } @Override public T get() throws InterruptedException, ExecutionException { lock.lock(); try { while (!this.completed) { condition.await(); } return getResult(); } finally { lock.unlock(); } } @Override public T get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { Args.notNull(unit, "Time unit"); final long msecs = unit.toMillis(timeout); final long startTime = (msecs <= 0) ? 0 : System.currentTimeMillis(); long waitTime = msecs; try { lock.lock(); if (this.completed) { return getResult(); } else if (waitTime <= 0) { throw TimeoutValueException.fromMilliseconds(msecs, msecs + Math.abs(waitTime)); } else { for (; ; ) { condition.await(waitTime, TimeUnit.MILLISECONDS); if (this.completed) { return getResult(); } waitTime = msecs - (System.currentTimeMillis() - startTime); if (waitTime <= 0) { throw TimeoutValueException.fromMilliseconds(msecs, msecs + Math.abs(waitTime)); } } } } finally { lock.unlock(); } } public boolean completed(final T result) { lock.lock(); try { if (this.completed) { return false; } this.completed = true; this.result = result; condition.signalAll(); } finally { lock.unlock(); } if (this.callback != null) { this.callback.completed(result); } return true; } public boolean failed(final Exception exception) { lock.lock(); try { if (this.completed) { return false; } this.completed = true; this.ex = exception; condition.signalAll(); } finally { lock.unlock(); } if (this.callback != null) { this.callback.failed(exception); } return true; } @Override public boolean cancel(final boolean mayInterruptIfRunning) { lock.lock(); try { if (this.completed) { return false; } this.completed = true; this.cancelled = true; condition.signalAll(); } finally { lock.unlock(); } if (this.callback != null) { this.callback.cancelled(); } return true; } @Override public boolean cancel() { return cancel(true); } } httpcore5/src/main/java/org/apache/hc/core5/concurrent/CallbackContribution.java0100644 0000000 0000000 00000004077 00000250600 026704 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.concurrent; /** * Abstracts implementations of {@link FutureCallback}s that contribute a result * of the operation to another {@link FutureCallback}. * * @param the future result type of an asynchronous operation. * @since 5.1 */ public abstract class CallbackContribution implements FutureCallback { private final FutureCallback callback; /** * Constructs a new instance for a FutureCallback. * * @param callback the FutureCallback, may be {@code null}. */ public CallbackContribution(final FutureCallback callback) { this.callback = callback; } @Override public final void failed(final Exception ex) { if (callback != null) { callback.failed(ex); } } @Override public final void cancelled() { if (callback != null) { callback.cancelled(); } } } httpcore5/src/main/java/org/apache/hc/core5/concurrent/Cancellable.java0100644 0000000 0000000 00000003156 00000250600 024772 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.concurrent; /** * A {@code Cancellable} represents a process or an operation that can be * canceled. * * @since 4.2 */ public interface Cancellable { /** * Cancels the ongoing operation or process. * * @return {@code true} if the operation or process has been cancelled as a result of * this method call or {@code false} if it has already been cancelled or not started. */ boolean cancel(); } httpcore5/src/main/java/org/apache/hc/core5/concurrent/CancellableDependency.java0100644 0000000 0000000 00000003460 00000250600 026767 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.concurrent; /** * This interface represents {@link Cancellable} object dependent on another * ongoing process or operation. * * @since 5.0 */ public interface CancellableDependency extends Cancellable { /** * Sets {@link Cancellable} dependency on another ongoing process or * operation represented by {@link Cancellable}. * * @param cancellable another ongoing process or operation. */ void setDependency(Cancellable cancellable); /** * Determines whether the process or operation has been cancelled. * * @return cancelled flag. */ boolean isCancelled(); } httpcore5/src/main/java/org/apache/hc/core5/concurrent/CompletedFuture.java0100644 0000000 0000000 00000004362 00000250600 025714 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.concurrent; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; /** * Immutable, completed future. * * @param the future result type of an asynchronous operation. * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class CompletedFuture implements Future, Cancellable { private final T result; public CompletedFuture(final T result) { super(); this.result = result; } @Override public boolean isCancelled() { return false; } @Override public boolean isDone() { return true; } @Override public T get() { return this.result; } @Override public T get(final long timeout, final TimeUnit unit) { return this.result; } @Override public boolean cancel(final boolean mayInterruptIfRunning) { return false; } @Override public boolean cancel() { return false; } } httpcore5/src/main/java/org/apache/hc/core5/concurrent/CompletingFutureContribution.java0100644 0000000 0000000 00000004754 00000250600 030506 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.concurrent; import java.util.Objects; import java.util.function.Function; /** * Completes a result on a {@link BasicFuture}. * * @param the future result type of an asynchronous operation for this type. * @param the future result type of an asynchronous operation for the {@link FutureCallback}. * @since 5.4 */ public class CompletingFutureContribution extends FutureContribution { private final Function resultProvider; /** * Constructs a new instance to callback the given {@link BasicFuture}. * * @param future The callback. */ @SuppressWarnings("unchecked") public CompletingFutureContribution(final BasicFuture future) { this(future, (Function) Function.identity()); } /** * Constructs a new instance to callback the given {@link BasicFuture}. * * @param future The callback. * @param resultProvider Provides the result to complete the future. */ public CompletingFutureContribution(final BasicFuture future, final Function resultProvider) { super(future); this.resultProvider = Objects.requireNonNull(resultProvider, "resultProvider"); } @Override public void completed(final T result) { getFuture().completed(resultProvider.apply(result)); } } httpcore5/src/main/java/org/apache/hc/core5/concurrent/ComplexCancellable.java0100644 0000000 0000000 00000005254 00000250600 026323 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.concurrent; import java.util.concurrent.atomic.AtomicMarkableReference; import org.apache.hc.core5.util.Args; /** * {@link Cancellable} that has a dependency on another {@link Cancellable} * process or operation. Dependent process or operation will get cancelled * if this {@link Cancellable} itself is cancelled. * * @since 5.0 */ public final class ComplexCancellable implements CancellableDependency { private final AtomicMarkableReference dependencyRef; public ComplexCancellable() { this.dependencyRef = new AtomicMarkableReference<>(null, false); } @Override public boolean isCancelled() { return dependencyRef.isMarked(); } @Override public void setDependency(final Cancellable dependency) { Args.notNull(dependency, "dependency"); while (!dependencyRef.compareAndSet(dependencyRef.getReference(), dependency, false, false)) { if (dependencyRef.isMarked()) { dependency.cancel(); return; } } } @Override public boolean cancel() { while (!dependencyRef.isMarked()) { final Cancellable actualDependency = dependencyRef.getReference(); if (dependencyRef.compareAndSet(actualDependency, actualDependency, false, true)) { if (actualDependency != null) { actualDependency.cancel(); } return true; } } return false; } } httpcore5/src/main/java/org/apache/hc/core5/concurrent/ComplexFuture.java0100644 0000000 0000000 00000006214 00000250600 025405 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.concurrent; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.util.Args; /** * {@link Future} whose result depends on another {@link Cancellable} process * or operation or another {@link Future}. Dependent process will get cancelled * if the future itself is cancelled. * * @param the future result type of an asynchronous operation. * @since 5.0 */ public final class ComplexFuture extends BasicFuture implements CancellableDependency { private final AtomicReference dependencyRef; public ComplexFuture(final FutureCallback callback) { super(callback); this.dependencyRef = new AtomicReference<>(); } @Override public void setDependency(final Cancellable dependency) { Args.notNull(dependency, "dependency"); if (isDone()) { dependency.cancel(); } else { dependencyRef.set(dependency); } } public void setDependency(final Future dependency) { Args.notNull(dependency, "dependency"); if (dependency instanceof Cancellable) { setDependency((Cancellable) dependency); } else { setDependency(() -> dependency.cancel(true)); } } @Override public boolean completed(final T result) { final boolean completed = super.completed(result); dependencyRef.set(null); return completed; } @Override public boolean failed(final Exception exception) { final boolean failed = super.failed(exception); dependencyRef.set(null); return failed; } @Override public boolean cancel(final boolean mayInterruptIfRunning) { final boolean cancelled = super.cancel(mayInterruptIfRunning); final Cancellable dependency = dependencyRef.getAndSet(null); if (dependency != null) { dependency.cancel(); } return cancelled; } } httpcore5/src/main/java/org/apache/hc/core5/concurrent/DefaultThreadFactory.java0100644 0000000 0000000 00000004427 00000250600 026653 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.concurrent; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicLong; /** * Default {@link ThreadFactory} implementation. * * @since 5.0 */ public class DefaultThreadFactory implements ThreadFactory { private final String namePrefix; private final ThreadGroup group; private final AtomicLong count; private final boolean daemon; public DefaultThreadFactory(final String namePrefix, final ThreadGroup group, final boolean daemon) { this.namePrefix = namePrefix; this.group = group; this.daemon = daemon; this.count = new AtomicLong(); } public DefaultThreadFactory(final String namePrefix, final boolean daemon) { this(namePrefix, null, daemon); } public DefaultThreadFactory(final String namePrefix) { this(namePrefix, null, false); } @Override public Thread newThread(final Runnable target) { final Thread thread = new Thread(this.group, target, this.namePrefix + "-" + this.count.incrementAndGet()); thread.setDaemon(daemon); return thread; } } httpcore5/src/main/java/org/apache/hc/core5/concurrent/FutureCallback.java0100644 0000000 0000000 00000003004 00000250600 025464 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.concurrent; /** * A callback interface that gets invoked upon completion of a {@link java.util.concurrent.Future}. * * @param the future result type consumed by this callback. * @since 4.2 */ public interface FutureCallback { void completed(T result); void failed(Exception ex); void cancelled(); } httpcore5/src/main/java/org/apache/hc/core5/concurrent/FutureContribution.java0100644 0000000 0000000 00000004177 00000250600 026463 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.concurrent; import java.util.Objects; /** * Convenience base class for {@link FutureCallback}s that contribute a result * of the operation to another {@link BasicFuture}. * * @param the future result type of an asynchronous operation for this type. * @since 5.1 */ public abstract class FutureContribution implements FutureCallback { private final BasicFuture future; /** * Constructs a new instance to callback the given {@link BasicFuture}. * * @param future The callback, non-null. */ public FutureContribution(final BasicFuture future) { this.future = Objects.requireNonNull(future); } @Override public final void failed(final Exception ex) { future.failed(ex); } @Override public final void cancelled() { future.cancel(); } @SuppressWarnings("unchecked") BasicFuture getFuture() { return (BasicFuture) future; } } httpcore5/src/main/java/org/apache/hc/core5/concurrent/package-info.java0100644 0000000 0000000 00000002365 00000250600 025132 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Core concurrency component APIs. */ package org.apache.hc.core5.concurrent; httpcore5/src/main/java/org/apache/hc/core5/function/0040755 0000000 0000000 00000000000 00000250600 021403 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/function/Callback.java0100644 0000000 0000000 00000002606 00000250600 023743 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.function; /** * Abstract callback. * * @param The type of object consumed by the callback. * @since 5.0 */ @FunctionalInterface public interface Callback { void execute(T object); } httpcore5/src/main/java/org/apache/hc/core5/function/Decorator.java0100644 0000000 0000000 00000002571 00000250600 024172 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.function; /** * Abstract decorator. * * @param The type of object to decorate. * @since 5.0 */ @FunctionalInterface public interface Decorator { T decorate(T object); } httpcore5/src/main/java/org/apache/hc/core5/function/Factory.java0100644 0000000 0000000 00000002665 00000250600 023663 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.function; /** * Abstract object factory. * * @param

The factory's input type. * @param The type of object produced by this factory. * @since 5.0 */ @FunctionalInterface public interface Factory { T create(P parameter); } httpcore5/src/main/java/org/apache/hc/core5/function/Resolver.java0100644 0000000 0000000 00000002635 00000250600 024052 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.function; /** * Abstract resolver from input to output. * * @param the input type. * @param the output type. * @since 5.0 */ @FunctionalInterface public interface Resolver { O resolve(I object); } httpcore5/src/main/java/org/apache/hc/core5/function/Supplier.java0100644 0000000 0000000 00000002715 00000250600 024053 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.function; /** * Represents a supplier of results. * * @param the type of results supplied by this supplier. * @since 5.0 */ @FunctionalInterface public interface Supplier { /** * Gets a result. * * @return a result */ T get(); } httpcore5/src/main/java/org/apache/hc/core5/function/package-info.java0100644 0000000 0000000 00000002367 00000250600 024577 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Commonly used functional interfaces. */ package org.apache.hc.core5.function; httpcore5/src/main/java/org/apache/hc/core5/http/0040755 0000000 0000000 00000000000 00000250600 020535 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/Chars.java0100644 0000000 0000000 00000003353 00000250600 022441 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * Commons chars used by HTTP/1.1 protocol. * * @since 5.0 */ public final class Chars { public static final int NULL = 0; // public static final int CR = 13; // public static final int LF = 10; // public static final int SP = 32; // public static final int HT = 9; // public static final int DEL = 127; // private Chars() { } } httpcore5/src/main/java/org/apache/hc/core5/http/ClassicHttpRequest.java0100644 0000000 0000000 00000002611 00000250600 025167 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * 'Classic' {@link HttpRequest} message that can enclose {@link HttpEntity}. * * @since 5.0 */ public interface ClassicHttpRequest extends HttpRequest, HttpEntityContainer { // empty } httpcore5/src/main/java/org/apache/hc/core5/http/ClassicHttpResponse.java0100644 0000000 0000000 00000002662 00000250600 025343 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.Closeable; /** * 'Classic' {@link HttpResponse} message that can enclose {@link HttpEntity}. * * @since 5.0 */ public interface ClassicHttpResponse extends HttpResponse, HttpEntityContainer, Closeable { // empty } httpcore5/src/main/java/org/apache/hc/core5/http/ConnectionClosedException.java0100644 0000000 0000000 00000005325 00000250600 026512 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.IOException; /** * Signals that the connection has been closed unexpectedly. * * @since 4.0 */ public class ConnectionClosedException extends IOException { private static final long serialVersionUID = 617550366255636674L; /** * Constructs a new ConnectionClosedException with a default message. */ public ConnectionClosedException() { super("Connection is closed"); } /** * Constructs a new ConnectionClosedException with the specified detail message. * * @param message The exception detail message */ public ConnectionClosedException(final String message) { super(HttpException.clean(message)); } /** * Constructs a new ConnectionClosedException with the specified detail message. * * @param format The exception detail message format; see {@link String#format(String, Object...)}. * @param args The exception detail message arguments; see {@link String#format(String, Object...)}. * * @since 5.0 */ public ConnectionClosedException(final String format, final Object... args) { super(HttpException.clean(String.format(format, args))); } /** * Constructs a {@code ConnectionClosedException} with the specified detail message * and cause. * * @param message The exception detail message * @param cause The cause. * * @since 5.0 */ public ConnectionClosedException(final String message, final Throwable cause) { super(message, cause); } } httpcore5/src/main/java/org/apache/hc/core5/http/ConnectionRequestTimeoutException.java0100644 0000000 0000000 00000003603 00000250600 030275 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.InterruptedIOException; /** * Signals timeout requesting connection. * * @since 5.0 */ public class ConnectionRequestTimeoutException extends InterruptedIOException { private static final long serialVersionUID = 1L; /** * Creates a {@link ConnectionRequestTimeoutException} without details. */ public ConnectionRequestTimeoutException() { super(); } /** * Creates a {@link ConnectionRequestTimeoutException} with a detail message. * * @param message the exception detail message, or {@code null} */ public ConnectionRequestTimeoutException(final String message) { super(HttpException.clean(message)); } } httpcore5/src/main/java/org/apache/hc/core5/http/ConnectionReuseStrategy.java0100644 0000000 0000000 00000006105 00000250600 026225 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.protocol.HttpContext; /** * Interface for deciding whether a connection can be re-used for * subsequent requests and should be kept alive. *

* Implementations of this interface must be thread-safe. Access to shared * data must be synchronized as methods of this interface may be executed * from multiple threads. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.STATELESS) public interface ConnectionReuseStrategy { /** * Decides whether a connection can be kept open after a request. * If this method returns {@code false}, the caller MUST * close the connection to correctly comply with the HTTP protocol. * If it returns {@code true}, the caller SHOULD attempt to * keep the connection open for reuse with another request. *

* One can use the HTTP context to retrieve additional objects that * may be relevant for the keep-alive strategy: the actual HTTP * connection, the original HTTP request, target host if known, * number of times the connection has been reused already and so on. *

*

* If the connection is already closed, {@code false} is returned. * The stale connection check MUST NOT be triggered by a * connection reuse strategy. *

* * @param request * The last request transmitted over that connection. * @param response * The last response transmitted over that connection. * @param context the context in which the connection is being * used. * * @return {@code true} if the connection is allowed to be reused, or * {@code false} if it MUST NOT be reused */ boolean keepAlive(HttpRequest request, HttpResponse response, HttpContext context); } httpcore5/src/main/java/org/apache/hc/core5/http/ContentLengthStrategy.java0100644 0000000 0000000 00000004427 00000250600 025703 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; /** * Represents a strategy to determine length of the enclosed content entity * based on properties of the HTTP message. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.STATELESS) public interface ContentLengthStrategy { /** * Message body chunk coded */ long CHUNKED = -1; /** * Message body not explicitly delineated. Legal for HTTP response messages * and illegal for HTTP request messages. */ long UNDEFINED = -Long.MAX_VALUE; /** * Returns length of the given message in bytes. The returned value * must be a non-negative number, {@link #CHUNKED} if the message is * chunk coded, or {@link #UNDEFINED} if the message is not explicitly * delineated. * * @param message HTTP message * @return content length, {@link #UNDEFINED}, or {@link #CHUNKED} * * @throws HttpException in case of HTTP protocol violation */ long determineLength(HttpMessage message) throws HttpException; } httpcore5/src/main/java/org/apache/hc/core5/http/ContentTooLongException.java0100644 0000000 0000000 00000004236 00000250600 026175 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.IOException; /** * Signals that HTTP entity content is too long. * * @since 4.2 */ public class ContentTooLongException extends IOException { private static final long serialVersionUID = -924287689552495383L; /** * Creates a new ContentTooLongException with the specified detail message. * * @param message exception message */ public ContentTooLongException(final String message) { super(HttpException.clean(message)); } /** * Constructs a new ContentTooLongException with the specified detail message. * * @param format The exception detail message format; see {@link String#format(String, Object...)}. * @param args The exception detail message arguments; see {@link String#format(String, Object...)}. * * @since 4.4.11 */ public ContentTooLongException(final String format, final Object... args) { super(HttpException.clean(String.format(format, args))); } } httpcore5/src/main/java/org/apache/hc/core5/http/ContentType.java0100644 0000000 0000000 00000047655 00000250600 023672 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.Serializable; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.StandardCharsets; import java.nio.charset.UnsupportedCharsetException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.message.BasicNameValuePair; import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.http.message.ParserCursor; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; import org.apache.hc.core5.util.TextUtils; /** * Content type information consisting of a MIME type and an optional charset. *

* This class makes no attempts to verify validity of the MIME type. * The input parameters of the {@link #create(String, String)} method, however, may not * contain characters {@code <">, <;>, <,>} reserved by the HTTP specification. * * @since 4.2 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public final class ContentType implements Serializable { private static final long serialVersionUID = -7768694718232371896L; /** * Param that represent {@code charset} constant. */ private static final String CHARSET = "charset"; // constants public static final ContentType APPLICATION_ATOM_XML = create( "application/atom+xml", StandardCharsets.UTF_8); public static final ContentType APPLICATION_FORM_URLENCODED = create( "application/x-www-form-urlencoded", StandardCharsets.ISO_8859_1); public static final ContentType APPLICATION_JSON = create( "application/json", StandardCharsets.UTF_8); /** * Public constant media type for ZIP archives {@code application/x-zip-compressed}. *

* Note that Windows uploads {@code .zip} files with this non-standard MIME type. *

* * @see ZIP archive application/x-zip-compressed * @since 5.4 */ public static final ContentType APPLICATION_ZIP_COMPRESSED = create("application/x-zip-compressed"); /** * Public constant media type for {@code application/x-ndjson}. * @since 5.1 */ public static final ContentType APPLICATION_NDJSON = create( "application/x-ndjson", StandardCharsets.UTF_8); public static final ContentType APPLICATION_OCTET_STREAM = create( "application/octet-stream", (Charset) null); /** * Public constant media type for {@code application/pdf}. * @since 5.1 */ public static final ContentType APPLICATION_PDF = create( "application/pdf", StandardCharsets.UTF_8); public static final ContentType APPLICATION_SOAP_XML = create( "application/soap+xml", StandardCharsets.UTF_8); public static final ContentType APPLICATION_SVG_XML = create( "application/svg+xml", StandardCharsets.UTF_8); public static final ContentType APPLICATION_XHTML_XML = create( "application/xhtml+xml", StandardCharsets.UTF_8); public static final ContentType APPLICATION_XML = create( "application/xml", StandardCharsets.UTF_8); /** * Public constant media type for {@code application/problem+json}. * @see Problem Details for HTTP APIs, 6.1. application/problem+json * @since 5.1 */ public static final ContentType APPLICATION_PROBLEM_JSON = create( "application/problem+json", StandardCharsets.UTF_8); /** * Public constant media type for {@code application/problem+xml}. * @see Problem Details for HTTP APIs, 6.2. application/problem+xml * @since 5.1 */ public static final ContentType APPLICATION_PROBLEM_XML = create( "application/problem+xml", StandardCharsets.UTF_8); /** * Public constant media type for {@code application/rss+xml}. * @since 5.1 */ public static final ContentType APPLICATION_RSS_XML = create( "application/rss+xml", StandardCharsets.UTF_8); public static final ContentType IMAGE_BMP = create( "image/bmp"); public static final ContentType IMAGE_GIF = create( "image/gif"); public static final ContentType IMAGE_JPEG = create( "image/jpeg"); public static final ContentType IMAGE_PNG = create( "image/png"); public static final ContentType IMAGE_SVG = create( "image/svg+xml"); public static final ContentType IMAGE_TIFF = create( "image/tiff"); public static final ContentType IMAGE_WEBP = create( "image/webp"); public static final ContentType MULTIPART_FORM_DATA = create( "multipart/form-data", StandardCharsets.ISO_8859_1); /** * Public constant media type for {@code multipart/mixed}. * @since 5.1 */ public static final ContentType MULTIPART_MIXED = create( "multipart/mixed", StandardCharsets.ISO_8859_1); /** * Public constant media type for {@code multipart/related}. * @since 5.1 */ public static final ContentType MULTIPART_RELATED = create( "multipart/related", StandardCharsets.ISO_8859_1); public static final ContentType TEXT_HTML = create( "text/html", StandardCharsets.UTF_8); /** * Public constant media type for {@code text/markdown}. * @since 5.1 */ public static final ContentType TEXT_MARKDOWN = create( "text/markdown", StandardCharsets.UTF_8); public static final ContentType TEXT_PLAIN = create( "text/plain", StandardCharsets.UTF_8); public static final ContentType TEXT_XML = create( "text/xml", StandardCharsets.UTF_8); /** * Public constant media type for {@code text/event-stream}. * @see Server-Sent Events W3C recommendation * @since 5.1 */ public static final ContentType TEXT_EVENT_STREAM = create( "text/event-stream", StandardCharsets.UTF_8); public static final ContentType WILDCARD = create( "*/*", (Charset) null); /** * An empty immutable {@code NameValuePair} array. */ private static final NameValuePair[] EMPTY_NAME_VALUE_PAIR_ARRAY = {}; /** * @deprecated To be removed in 6.0 */ @Deprecated private static final Map CONTENT_TYPE_MAP; static { final ContentType[] contentTypes = { APPLICATION_ATOM_XML, APPLICATION_FORM_URLENCODED, APPLICATION_JSON, APPLICATION_SVG_XML, APPLICATION_XHTML_XML, APPLICATION_XML, IMAGE_BMP, IMAGE_GIF, IMAGE_JPEG, IMAGE_PNG, IMAGE_SVG, IMAGE_TIFF, IMAGE_WEBP, MULTIPART_FORM_DATA, TEXT_HTML, TEXT_PLAIN, TEXT_XML }; final HashMap map = new HashMap<>(); for (final ContentType contentType: contentTypes) { map.put(contentType.getMimeType(), contentType); } CONTENT_TYPE_MAP = Collections.unmodifiableMap(map); } // defaults public static final ContentType DEFAULT_TEXT = TEXT_PLAIN; public static final ContentType DEFAULT_BINARY = APPLICATION_OCTET_STREAM; private final String mimeType; private final Charset charset; private final NameValuePair[] params; ContentType( final String mimeType, final Charset charset) { this.mimeType = mimeType; this.charset = charset; this.params = null; } ContentType( final String mimeType, final Charset charset, final NameValuePair[] params) { this.mimeType = mimeType; this.charset = charset; this.params = params; } public String getMimeType() { return this.mimeType; } public Charset getCharset() { return this.charset; } /** * Gets this Charset if it's non-null, otherwise, return the given {@code defaultCharset}. * * @param defaultCharset A default Charset. * @return this Charset if it's non-null, or the given {@code defaultCharset}. * @since 5.2 */ public Charset getCharset(final Charset defaultCharset) { return this.charset != null ? this.charset : defaultCharset; } /** * Gets the named parameter's value. * * @param name The parameter name. * @return The parameter value. * @since 4.3 */ public String getParameter(final String name) { Args.notEmpty(name, "Parameter name"); if (this.params == null) { return null; } for (final NameValuePair param: this.params) { if (param.getName().equalsIgnoreCase(name)) { return param.getValue(); } } return null; } /** * Generates textual representation of this content type which can be used as the value * of a {@code Content-Type} header. */ @Override public String toString() { final CharArrayBuffer buf = new CharArrayBuffer(64); buf.append(this.mimeType); if (this.params != null) { buf.append("; "); MessageSupport.formatParameters(buf, this.params); } else if (this.charset != null) { buf.append("; charset="); buf.append(this.charset.name()); } return buf.toString(); } private static boolean valid(final String s) { for (int i = 0; i < s.length(); i++) { final char ch = s.charAt(i); if (ch == '"' || ch == ',' || ch == ';') { return false; } } return true; } /** * Creates a new instance of {@link ContentType}. * * @param mimeType MIME type. It may not be {@code null} or empty. It may not contain * characters {@code <">, <;>, <,>} reserved by the HTTP specification. * @param charset charset. * @return content type */ public static ContentType create(final String mimeType, final Charset charset) { final String normalizedMimeType = TextUtils.toLowerCase(Args.notBlank(mimeType, "MIME type")); Args.check(valid(normalizedMimeType), "MIME type may not contain reserved characters"); return new ContentType(normalizedMimeType, charset); } /** * Creates a new instance of {@link ContentType} without a charset. * * @param mimeType MIME type. It may not be {@code null} or empty. It may not contain * characters {@code <">, <;>, <,>} reserved by the HTTP specification. * @return content type */ public static ContentType create(final String mimeType) { return create(mimeType, (Charset) null); } /** * Creates a new instance of {@link ContentType}. * * @param mimeType MIME type. It may not be {@code null} or empty. It may not contain * characters {@code <">, <;>, <,>} reserved by the HTTP specification. * @param charset charset. It may not contain characters {@code <">, <;>, <,>} reserved by the HTTP * specification. This parameter is optional. * @return content type * @throws UnsupportedCharsetException Thrown when the named charset is not available in * this instance of the Java virtual machine */ public static ContentType create( final String mimeType, final String charset) throws UnsupportedCharsetException { return create(mimeType, !TextUtils.isBlank(charset) ? Charset.forName(charset) : null); } private static ContentType create(final HeaderElement helem, final boolean strict) { final String mimeType = helem.getName(); if (TextUtils.isBlank(mimeType)) { return null; } return create(helem.getName(), helem.getParameters(), strict); } private static ContentType create(final String mimeType, final NameValuePair[] params, final boolean strict) { Charset charset = null; if (params != null) { for (final NameValuePair param : params) { if (param.getName().equalsIgnoreCase(CHARSET)) { final String s = param.getValue(); if (!TextUtils.isBlank(s)) { try { charset = Charset.forName(s); } catch (final UnsupportedCharsetException | IllegalCharsetNameException ex) { if (strict) { throw ex; } } } break; } } } return new ContentType(mimeType, charset, params != null && params.length > 0 ? params : null); } /** * Creates a new instance of {@link ContentType} with the given parameters. * * @param mimeType MIME type. It may not be {@code null} or empty. It may not contain * characters {@code <">, <;>, <,>} reserved by the HTTP specification. * @param params parameters. * @return content type * @throws UnsupportedCharsetException If no support for a named Charset is available * in this instance of the Java virtual machine. * @since 4.4 */ public static ContentType create( final String mimeType, final NameValuePair... params) throws UnsupportedCharsetException { final String type = TextUtils.toLowerCase(Args.notBlank(mimeType, "MIME type")); Args.check(valid(type), "MIME type may not contain reserved characters"); return create(mimeType, params != null ? params.clone() : null, true); } /** * Parses textual representation of {@code Content-Type} value. * * @param s text * @return content type {@code Content-Type} value or null. * @throws UnsupportedCharsetException Thrown when the named charset is not available in * this instance of the Java virtual machine */ public static ContentType parse(final CharSequence s) throws UnsupportedCharsetException { return parse(s, true); } /** * Parses textual representation of {@code Content-Type} value ignoring invalid charsets. * * @param s text * @return content type {@code Content-Type} value or null. */ public static ContentType parseLenient(final CharSequence s) { return parse(s, false); } private static ContentType parse(final CharSequence s, final boolean strict) throws UnsupportedCharsetException { if (TextUtils.isBlank(s)) { return null; } final ParserCursor cursor = new ParserCursor(0, s.length()); final AtomicReference firstElementRef = new AtomicReference<>(); MessageSupport.parseElements(s, cursor, e -> firstElementRef.compareAndSet(null, e)); final HeaderElement element = firstElementRef.get(); if (element != null) { return create(element, strict); } return null; } /** * Returns {@code Content-Type} for the given MIME type. * * @param mimeType MIME type * @return content type or {@code null} if not known. * * @since 4.5 * * @deprecated Do not use. This method was made public by mistake. */ @Deprecated public static ContentType getByMimeType(final String mimeType) { if (mimeType == null) { return null; } return CONTENT_TYPE_MAP.get(mimeType); } /** * Gets a ContentType's Charset if neither are null, otherwise, return the given {@code defaultCharset}. * * @param contentType the ContentType to test and query. * @param defaultCharset a default Charset. * @return the ContentType's Charset if neither are null, otherwise, return the given {@code defaultCharset}. * @since 5.2 */ public static Charset getCharset(final ContentType contentType, final Charset defaultCharset) { return contentType != null ? contentType.getCharset(defaultCharset) : defaultCharset; } /** * Creates a new instance with this MIME type and the given Charset. * * @param charset charset * @return a new instance with this MIME type and the given Charset. * @since 4.3 */ public ContentType withCharset(final Charset charset) { return create(this.getMimeType(), charset); } /** * Creates a new instance with this MIME type and the given Charset name. * * @param charset name * @return a new instance with this MIME type and the given Charset name. * @throws UnsupportedCharsetException Thrown when the named charset is not available in * this instance of the Java virtual machine * @since 4.3 */ public ContentType withCharset(final String charset) { return create(this.getMimeType(), charset); } /** * Creates a new instance with this MIME type and the given parameters. * * @param params parameters. * @return a new instance with this MIME type and the given parameters. * @throws UnsupportedCharsetException If no support for a named Charset is available * in this instance of the Java virtual machine. * @since 4.4 */ public ContentType withParameters( final NameValuePair... params) throws UnsupportedCharsetException { if (params.length == 0) { return this; } final Map paramMap = new LinkedHashMap<>(); if (this.params != null) { for (final NameValuePair param: this.params) { paramMap.put(param.getName(), param.getValue()); } } for (final NameValuePair param: params) { paramMap.put(param.getName(), param.getValue()); } final List newParams = new ArrayList<>(paramMap.size() + 1); if (this.charset != null && !paramMap.containsKey(CHARSET)) { newParams.add(new BasicNameValuePair(CHARSET, this.charset.name())); } for (final Map.Entry entry: paramMap.entrySet()) { newParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); } return create(this.getMimeType(), newParams.toArray(EMPTY_NAME_VALUE_PAIR_ARRAY), true); } public boolean isSameMimeType(final ContentType contentType) { return contentType != null && mimeType.equalsIgnoreCase(contentType.getMimeType()); } } httpcore5/src/main/java/org/apache/hc/core5/http/EndpointDetails.java0100644 0000000 0000000 00000006337 00000250600 024474 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.net.SocketAddress; import org.apache.hc.core5.net.InetAddressUtils; import org.apache.hc.core5.util.Timeout; /** * HTTP connection endpoint details. * * @since 5.0 */ public abstract class EndpointDetails implements HttpConnectionMetrics { private final SocketAddress remoteAddress; private final SocketAddress localAddress; private final Timeout socketTimeout; protected EndpointDetails(final SocketAddress remoteAddress, final SocketAddress localAddress, final Timeout socketTimeout) { this.remoteAddress = remoteAddress; this.localAddress = localAddress; this.socketTimeout = socketTimeout; } public SocketAddress getRemoteAddress() { return remoteAddress; } public SocketAddress getLocalAddress() { return localAddress; } /** * Gets the number of requests transferred over the connection, * 0 if not available. */ @Override public abstract long getRequestCount(); /** * Gets the number of responses transferred over the connection, * 0 if not available. */ @Override public abstract long getResponseCount(); /** * Gets the number of bytes transferred over the connection, * 0 if not available. */ @Override public abstract long getSentBytesCount(); /** * Gets the number of bytes transferred over the connection, * 0 if not available. */ @Override public abstract long getReceivedBytesCount(); /** * Gets the socket timeout. * * @return the socket timeout. */ public Timeout getSocketTimeout() { return socketTimeout; } @Override public String toString() { // Make enough room for two IPv6 addresses to avoid re-allocation in the StringBuilder. final StringBuilder buffer = new StringBuilder(90); InetAddressUtils.formatAddress(buffer, localAddress); buffer.append("<->"); InetAddressUtils.formatAddress(buffer, remoteAddress); return buffer.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/EntityDetails.java0100644 0000000 0000000 00000004456 00000250600 024170 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.util.Set; /** * Details of an entity transmitted by a message. * * @since 5.0 */ public interface EntityDetails { /** * Gets length of this entity, if known. * * @return the length of this entity, may be {@code 0}. */ long getContentLength(); /** * Gets content type of this entity, if known. * * @return the content type of this entity, may be {@code null}. */ String getContentType(); /** * Gets content encoding of this entity, if known. * * @return the content encoding of this entity, may be {@code null}. */ String getContentEncoding(); /** * Tests the chunked transfer hint for this entity. *

* The behavior of wrapping entities is implementation dependent, * but should respect the primary purpose. *

* @return the chunked transfer hint for this entity. */ boolean isChunked(); /** * Gets the preliminary declaration of trailing headers. * * @return the preliminary declaration of trailing headers. */ Set getTrailerNames(); } httpcore5/src/main/java/org/apache/hc/core5/http/ExceptionListener.java0100644 0000000 0000000 00000004153 00000250600 025044 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; /** * @since 4.4 */ @Contract(threading = ThreadingBehavior.STATELESS) public interface ExceptionListener { ExceptionListener NO_OP = new ExceptionListener() { @Override public void onError(final Exception ex) { // no-op } @Override public void onError(final HttpConnection connection, final Exception ex) { // no-op } }; ExceptionListener STD_ERR = new ExceptionListener() { @Override public void onError(final Exception ex) { ex.printStackTrace(); } @Override public void onError(final HttpConnection connection, final Exception ex) { ex.printStackTrace(); } }; void onError(Exception ex); void onError(HttpConnection connection, Exception ex); } httpcore5/src/main/java/org/apache/hc/core5/http/FormattedHeader.java0100644 0000000 0000000 00000004150 00000250600 024433 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import org.apache.hc.core5.util.CharArrayBuffer; /** * An HTTP header which is already formatted. * For example when headers are received, the original formatting * can be preserved. This allows for the header to be sent without * another formatting step. * * @since 4.0 */ public interface FormattedHeader extends Header { /** * Obtains the buffer with the formatted header. * The returned buffer MUST NOT be modified. * * @return the formatted header, in a buffer that must not be modified */ CharArrayBuffer getBuffer(); /** * Obtains the start of the header value in the {@link #getBuffer buffer}. * By accessing the value in the buffer, creation of a temporary string * can be avoided. * * @return index of the first character of the header value * in the buffer returned by {@link #getBuffer getBuffer}. */ int getValuePos(); } httpcore5/src/main/java/org/apache/hc/core5/http/Header.java0100644 0000000 0000000 00000003321 00000250600 022564 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * Represents an HTTP header field consisting of a field name and a field * value. * * @since 4.0 */ public interface Header extends NameValuePair { /** * Returns {@code true} if the header should be considered sensitive. *

* Some encoding schemes such as HPACK impose restrictions on encoded * representation of sensitive headers. *

* * @return {@code true} if the header should be considered sensitive. * * @since 5.0 */ boolean isSensitive(); } httpcore5/src/main/java/org/apache/hc/core5/http/HeaderElement.java0100644 0000000 0000000 00000004431 00000250600 024101 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * Represents an element of an HTTP {@link Header header} value consisting of * a name / value pair and a number of optional name / value parameters. * * @since 4.0 */ public interface HeaderElement { /** * Returns header element name. * * @return header element name */ String getName(); /** * Returns header element value. * * @return header element value */ String getValue(); /** * Returns an array of name / value pairs. * * @return array of name / value pairs */ NameValuePair[] getParameters(); /** * Returns the first parameter with the given name. * * @param name parameter name * * @return name / value pair */ NameValuePair getParameterByName(String name); /** * Returns the total count of parameters. * * @return parameter count */ int getParameterCount(); /** * Returns parameter with the given index. * * @param index index * @return name / value pair */ NameValuePair getParameter(int index); } httpcore5/src/main/java/org/apache/hc/core5/http/HeaderElements.java0100644 0000000 0000000 00000003162 00000250600 024264 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * Constants for frequently used Header elements. * * @since 5.0 */ public final class HeaderElements { private HeaderElements() { } public static final String CHUNKED_ENCODING = "chunked"; public static final String CLOSE = "close"; public static final String KEEP_ALIVE = "keep-alive"; public static final String UPGRADE = "upgrade"; public static final String CONTINUE = "100-continue"; } httpcore5/src/main/java/org/apache/hc/core5/http/HttpConnection.java0100644 0000000 0000000 00000006217 00000250600 024342 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.IOException; import java.net.SocketAddress; import javax.net.ssl.SSLSession; /** * A generic HTTP connection, useful on client and server side. * * @since 4.0 */ public interface HttpConnection extends SocketModalCloseable { /** * Closes this connection gracefully. This method will attempt to flush the internal output * buffer prior to closing the underlying socket. This method MUST NOT be called from a * different thread to force shutdown of the connection. Use {@link #close(CloseMode) close(CloseMode.IMMEDIATE)} instead. */ @Override void close() throws IOException; /** * Returns this connection's endpoint details. * * @return this connection's endpoint details. */ EndpointDetails getEndpointDetails(); /** * Returns this connection's local address or {@code null} if it is not bound yet. * * @return this connection's local address or {@code null} if it is not bound yet. * @since 5.0 */ SocketAddress getLocalAddress(); /** * Returns this connection's remote address or {@code null} if it is not connected yet or * unconnected. * * @return this connection's remote address or {@code null} if it is not connected yet or * unconnected. * @since 5.0 */ SocketAddress getRemoteAddress(); /** * Returns this connection's protocol version or {@code null} if unknown. * * @return this connection's protocol version or {@code null} if unknown. * @since 5.0 */ ProtocolVersion getProtocolVersion(); /** * Returns this connection's SSL session or {@code null} if TLS has not been activated. * * @return this connection's SSL session or {@code null} if TLS has not been activated. */ SSLSession getSSLSession(); /** * Checks if this connection is open. * * @return true if it is open, false if it is closed. */ boolean isOpen(); } httpcore5/src/main/java/org/apache/hc/core5/http/HttpConnectionMetrics.java0100644 0000000 0000000 00000003604 00000250600 025666 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * The point of access to the statistics of an {@link HttpConnection}. * * @since 4.0 */ public interface HttpConnectionMetrics { /** * Gets the number of requests transferred over the connection, * 0 if not available. */ long getRequestCount(); /** * Gets the number of responses transferred over the connection, * 0 if not available. */ long getResponseCount(); /** * Gets the number of bytes transferred over the connection, * 0 if not available. */ long getSentBytesCount(); /** * Gets the number of bytes transferred over the connection, * 0 if not available. */ long getReceivedBytesCount(); } httpcore5/src/main/java/org/apache/hc/core5/http/HttpEntity.java0100644 0000000 0000000 00000013342 00000250600 023514 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; import org.apache.hc.core5.function.Supplier; /** * An entity that can be sent or received with an HTTP message. *

* There are three distinct types of entities in HttpCore, * depending on where their {@link #getContent content} originates: *

*
    *
  • streamed: The content is received from a stream, or * generated on the fly. In particular, this category includes * entities being received from a {@link HttpConnection connection}. * {@link #isStreaming Streamed} entities are generally not * {@link #isRepeatable repeatable}. *
  • *
  • self-contained: The content is in memory or obtained by * means that are independent from a connection or other entity. * Self-contained entities are generally {@link #isRepeatable repeatable}. *
  • *
  • wrapping: The content is obtained from another entity. *
  • *
*

* This distinction is important for connection management with incoming * entities. For entities that are created by an application and only sent * using the HTTP components framework, the difference between streamed * and self-contained is of little importance. In that case, it is suggested * to consider non-repeatable entities as streamed, and those that are * repeatable (without a huge effort) as self-contained. *

* * @since 4.0 */ public interface HttpEntity extends EntityDetails, Closeable { /** * Tests if the entity is capable of producing its data more than once. * A repeatable entity's getContent() and writeTo(OutputStream) methods * can be called more than once whereas a non-repeatable entity's can not. * @return true if the entity is repeatable, false otherwise. */ boolean isRepeatable(); /** * Gets a content stream of the entity. * {@link #isRepeatable Repeatable} entities are expected * to create a new instance of {@link InputStream} for each invocation * of this method and therefore can be consumed multiple times. * Entities that are not {@link #isRepeatable repeatable} are expected * to return the same {@link InputStream} instance and therefore * may not be consumed more than once. *

* If this entity belongs to an incoming HTTP message, calling * {@link InputStream#close()} on the returned {@code InputStream} will * try to consume the complete entity content to keep the connection * alive. In cases where this is undesired, e.g. when only a small part * of the content is relevant and consuming the complete entity content * would be too inefficient, only the HTTP message from which * this entity was obtained should be closed (if supported). *

*

* IMPORTANT: Please note all entity implementations must ensure that * all allocated resources are properly deallocated after * the {@link InputStream#close()} method is invoked. *

* @return content stream of the entity. * * @throws IOException if the stream could not be created * @throws UnsupportedOperationException * if entity content cannot be represented as {@link InputStream}. * * @see #isRepeatable() */ InputStream getContent() throws IOException, UnsupportedOperationException; /** * Writes the entity content out to the output stream. *

* IMPORTANT: Please note all entity implementations must ensure that * all allocated resources are properly deallocated when this method * returns. *

* * @param outStream the output stream to write entity content to * * @throws IOException if an I/O error occurs */ void writeTo(OutputStream outStream) throws IOException; /** * Tests whether this entity depends on an underlying stream. * Streamed entities that read data directly from the socket should * return {@code true}. Self-contained entities should return * {@code false}. Wrapping entities should delegate this call * to the wrapped entity. * * @return {@code true} if the entity content is streamed, * {@code false} otherwise */ boolean isStreaming(); // don't expect an exception here /** * Gets supplier of message trailers - headers sent after message body. * May return {@code null} if trailers are not available. * * @since 5.0 */ Supplier> getTrailers(); } httpcore5/src/main/java/org/apache/hc/core5/http/HttpEntityContainer.java0100644 0000000 0000000 00000003526 00000250600 025362 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * Contains an {@link HttpEntity}. * * @since 5.0 */ public interface HttpEntityContainer { /** * Obtains the message entity, if available. * * @return the message entity, or {@code null} if not available */ HttpEntity getEntity(); /** * Sets an entity for this message. *

* Please note that if an entity has already been set it is responsibility of the caller * to ensure release of the resources that may be associated with that entity. *

* * @param entity the entity to set of this message, or {@code null} to unset */ void setEntity(HttpEntity entity); } httpcore5/src/main/java/org/apache/hc/core5/http/HttpException.java0100644 0000000 0000000 00000007621 00000250600 024201 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * Signals that an HTTP exception has occurred. * * @since 4.0 */ public class HttpException extends Exception { private static final int FIRST_VALID_CHAR = Chars.SP; private static final long serialVersionUID = -5437299376222011036L; /** * Cleans the given String by converting characters with values less than 32 to equivalent hexadecimal codes. * * @param message the source string. * @return a converted string. */ static String clean(final String message) { final char[] chars = message.toCharArray(); int i; // First check to see if need to allocate a new StringBuilder for (i = 0; i < chars.length; i++) { if (chars[i] < FIRST_VALID_CHAR) { break; } } if (i == chars.length) { return message; } final StringBuilder builder = new StringBuilder(chars.length * 2); for (i = 0; i < chars.length; i++) { final char ch = chars[i]; if (ch < FIRST_VALID_CHAR) { builder.append("[0x"); final String hexString = Integer.toHexString(i); if (hexString.length() == 1) { builder.append("0"); } builder.append(hexString); builder.append("]"); } else { builder.append(ch); } } return builder.toString(); } /** * Creates a new HttpException with a {@code null} detail message. */ public HttpException() { super(); } /** * Creates a new HttpException with the specified detail message. * * @param message the exception detail message */ public HttpException(final String message) { super(clean(message)); } /** * Constructs a new HttpException with the specified detail message. * * @param format The exception detail message format; see {@link String#format(String, Object...)}. * @param args The exception detail message arguments; see {@link String#format(String, Object...)}. * * @since 5.0 */ public HttpException(final String format, final Object... args) { super(HttpException.clean(String.format(format, args))); } /** * Creates a new HttpException with the specified detail message and cause. * * @param message the exception detail message * @param cause the {@code Throwable} that caused this exception, or {@code null} * if the cause is unavailable, unknown, or not a {@code Throwable} */ public HttpException(final String message, final Throwable cause) { super(clean(message)); initCause(cause); } } httpcore5/src/main/java/org/apache/hc/core5/http/HttpHeaders.java0100644 0000000 0000000 00000016023 00000250600 023612 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * Constants enumerating standard and common HTTP headers. * * @since 4.1 */ public final class HttpHeaders { private HttpHeaders() { // Don't allow instantiation. } public static final String ACCEPT = "Accept"; public static final String ACCEPT_CHARSET = "Accept-Charset"; public static final String ACCEPT_ENCODING = "Accept-Encoding"; public static final String ACCEPT_LANGUAGE = "Accept-Language"; public static final String ACCEPT_RANGES = "Accept-Ranges"; /** * The CORS {@code Access-Control-Allow-Credentials} response header field name. */ public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; /** * The CORS {@code Access-Control-Allow-Headers} response header field name. */ public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; /** * The CORS {@code Access-Control-Allow-Methods} response header field name. */ public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; /** * The CORS {@code Access-Control-Allow-Origin} response header field name. */ public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; /** * The CORS {@code Access-Control-Expose-Headers} response header field name. */ public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; /** * The CORS {@code Access-Control-Max-Age} response header field name. */ public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; /** * The CORS {@code Access-Control-Request-Headers} request header field name. */ public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; /** * The CORS {@code Access-Control-Request-Method} request header field name. */ public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; public static final String AGE = "Age"; public static final String ALLOW = "Allow"; public static final String AUTHORIZATION = "Authorization"; public static final String CACHE_CONTROL = "Cache-Control"; public static final String CONNECTION = "Connection"; public static final String CONTENT_ENCODING = "Content-Encoding"; /** * The HTTP {@code Content-Disposition} header field name. */ public static final String CONTENT_DISPOSITION = "Content-Disposition"; public static final String CONTENT_LANGUAGE = "Content-Language"; public static final String CONTENT_LENGTH = "Content-Length"; public static final String CONTENT_LOCATION = "Content-Location"; public static final String CONTENT_MD5 = "Content-MD5"; public static final String CONTENT_RANGE = "Content-Range"; public static final String CONTENT_TYPE = "Content-Type"; /** * The HTTP {@code Cookie} header field name. */ public static final String COOKIE = "Cookie"; public static final String DATE = "Date"; public static final String DAV = "Dav"; public static final String DEPTH = "Depth"; public static final String DESTINATION = "Destination"; public static final String ETAG = "ETag"; public static final String EXPECT = "Expect"; public static final String EXPIRES = "Expires"; public static final String FROM = "From"; public static final String HOST = "Host"; public static final String IF = "If"; public static final String IF_MATCH = "If-Match"; public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; public static final String IF_NONE_MATCH = "If-None-Match"; public static final String IF_RANGE = "If-Range"; public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; public static final String KEEP_ALIVE = "Keep-Alive"; public static final String LAST_MODIFIED = "Last-Modified"; /** * The HTTP {@code Link} header field name. */ public static final String LINK = "Link"; public static final String LOCATION = "Location"; public static final String LOCK_TOKEN = "Lock-Token"; public static final String MAX_FORWARDS = "Max-Forwards"; public static final String OVERWRITE = "Overwrite"; public static final String PRAGMA = "Pragma"; public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate"; public static final String PROXY_AUTHORIZATION = "Proxy-Authorization"; public static final String PROXY_CONNECTION = "Proxy-Connection"; public static final String RANGE = "Range"; public static final String REFERER = "Referer"; public static final String RETRY_AFTER = "Retry-After"; public static final String SERVER = "Server"; public static final String STATUS_URI = "Status-URI"; /** * The HTTP {@code Set-Cookie} header field name. */ public static final String SET_COOKIE = "Set-Cookie"; public static final String TE = "TE"; public static final String TIMEOUT = "Timeout"; public static final String TRAILER = "Trailer"; public static final String TRANSFER_ENCODING = "Transfer-Encoding"; public static final String UPGRADE = "Upgrade"; public static final String USER_AGENT = "User-Agent"; public static final String VARY = "Vary"; public static final String VIA = "Via"; public static final String WARNING = "Warning"; public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; /** * RFC 7639 — ALPN HTTP header field used with CONNECT to advertise the * application protocols intended to run inside the tunnel (e.g. {@code h2}, {@code http/1.1}). * * @since 5.4 */ public static final String ALPN = "ALPN"; /** * The HTTP {@code Priority} header field name. */ public static final String PRIORITY = "Priority"; } httpcore5/src/main/java/org/apache/hc/core5/http/HttpHost.java0100644 0000000 0000000 00000030251 00000250600 023153 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.Serializable; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import java.util.Objects; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.net.Host; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.LangUtils; import org.apache.hc.core5.util.TextUtils; /** * Component that holds all details needed to describe an HTTP connection * to a host. This includes remote host name, port and protocol scheme. * * @see org.apache.hc.core5.net.Host * * @since 4.0 * @since 5.0 For constructors that take a scheme as an argument, that argument is now the first one. */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public final class HttpHost implements NamedEndpoint, Serializable { private static final long serialVersionUID = -7529410654042457626L; /** The default scheme is "http". */ public static final URIScheme DEFAULT_SCHEME = URIScheme.HTTP; private final String schemeName; private final Host host; private final InetAddress address; /** * Creates a new {@link HttpHost HttpHost}, specifying all values. * Constructor for HttpHost. * @param scheme the name of the scheme. * {@code null} indicates the * {@link #DEFAULT_SCHEME default scheme} * @param address the inet address. Can be {@code null} * @param hostname the hostname (IP or DNS name) * @param port the port number. * {@code -1} indicates the scheme default port. * * @throws IllegalArgumentException * If the port parameter is outside the specified range of valid port values, which is between 0 and * 65535, inclusive. {@code -1} indicates the scheme default port. * * @since 5.0 */ public HttpHost(final String scheme, final InetAddress address, final String hostname, final int port) { Args.containsNoBlanks(hostname, "Host name"); this.host = new Host(hostname, port); this.schemeName = scheme != null ? TextUtils.toLowerCase(scheme) : DEFAULT_SCHEME.id; this.address = address; } /** * Creates {@code HttpHost} instance with the given scheme, hostname and port. * @param scheme the name of the scheme. * {@code null} indicates the * {@link #DEFAULT_SCHEME default scheme} * @param hostname the hostname (IP or DNS name) * @param port the port number. * {@code -1} indicates the scheme default port. * * @throws IllegalArgumentException * If the port parameter is outside the specified range of valid port values, which is between 0 and * 65535, inclusive. {@code -1} indicates the scheme default port. */ public HttpHost(final String scheme, final String hostname, final int port) { this(scheme, null, hostname, port); } /** * Creates {@code HttpHost} instance with the default scheme and the given hostname and port. * * @param hostname the hostname (IP or DNS name) * @param port the port number. * {@code -1} indicates the scheme default port. * * @throws IllegalArgumentException * If the port parameter is outside the specified range of valid port values, which is between 0 and * 65535, inclusive. {@code -1} indicates the scheme default port. */ public HttpHost(final String hostname, final int port) { this(null, hostname, port); } /** * Creates {@code HttpHost} instance with the given hostname and scheme and the default port for that scheme. * @param scheme the name of the scheme. * {@code null} indicates the * {@link #DEFAULT_SCHEME default scheme} * @param hostname the hostname (IP or DNS name) * * @throws IllegalArgumentException * If the port parameter is outside the specified range of valid port values, which is between 0 and * 65535, inclusive. {@code -1} indicates the scheme default port. */ public HttpHost(final String scheme, final String hostname) { this(scheme, hostname, -1); } /** * Creates {@code HttpHost} instance from a string. Text may not contain any blanks. * * @throws URISyntaxException Thrown when a string could not be parsed as a URI reference. * @since 4.4 */ public static HttpHost create(final String s) throws URISyntaxException { Args.notEmpty(s, "HTTP Host"); String text = s; String scheme = null; final int schemeIdx = text.indexOf("://"); if (schemeIdx > 0) { scheme = text.substring(0, schemeIdx); if (TextUtils.containsBlanks(scheme)) { throw new URISyntaxException(s, "scheme contains blanks"); } text = text.substring(schemeIdx + 3); } final Host host = Host.create(text); return new HttpHost(scheme, host); } /** * Creates an {@code HttpHost} instance from the scheme, host, and port from the given URI. Other URI elements are ignored. * * @param uri scheme, host, and port. * @return a new HttpHost * * @since 5.0 */ public static HttpHost create(final URI uri) { final String scheme = uri.getScheme(); return new HttpHost(scheme != null ? scheme : URIScheme.HTTP.getId(), uri.getHost(), uri.getPort()); } /** * Creates {@code HttpHost} instance with the default scheme and port and the given hostname. * * @param hostname the hostname (IP or DNS name) * * @throws IllegalArgumentException * If the port parameter is outside the specified range of valid port values, which is between 0 and * 65535, inclusive. {@code -1} indicates the scheme default port. */ public HttpHost(final String hostname) { this(null, hostname, -1); } /** * Creates {@code HttpHost} instance with the given scheme, inet address and port. * @param scheme the name of the scheme. * {@code null} indicates the * {@link #DEFAULT_SCHEME default scheme} * @param address the inet address. * @param port the port number. * {@code -1} indicates the scheme default port. * * @throws IllegalArgumentException * If the port parameter is outside the specified range of valid port values, which is between 0 and * 65535, inclusive. {@code -1} indicates the scheme default port. * * @since 5.0 */ public HttpHost(final String scheme, final InetAddress address, final int port) { this(scheme, Args.notNull(address,"Inet address"), address.getHostName(), port); } /** * Creates {@code HttpHost} instance with the default scheme and the given inet address * and port. * * @param address the inet address. * @param port the port number. * {@code -1} indicates the scheme default port. * * @throws IllegalArgumentException * If the port parameter is outside the specified range of valid port values, which is between 0 and * 65535, inclusive. {@code -1} indicates the scheme default port. * * @since 4.3 */ public HttpHost(final InetAddress address, final int port) { this(null, address, port); } /** * Creates {@code HttpHost} instance with the default scheme and port and the given inet * address. * * @param address the inet address. * * @throws IllegalArgumentException * If the port parameter is outside the specified range of valid port values, which is between 0 and * 65535, inclusive. {@code -1} indicates the scheme default port. * * @since 4.3 */ public HttpHost(final InetAddress address) { this(null, address, -1); } /** * @throws IllegalArgumentException * If the port parameter is outside the specified range of valid port values, which is between 0 and * 65535, inclusive. {@code -1} indicates the scheme default port. * * @since 5.0 */ public HttpHost(final String scheme, final NamedEndpoint namedEndpoint) { this(scheme, Args.notNull(namedEndpoint, "Named endpoint").getHostName(), namedEndpoint.getPort()); } /** * @since 5.0 * * @deprecated Use {@link HttpHost#HttpHost(String, NamedEndpoint)} */ @Deprecated public HttpHost(final URIAuthority authority) { this(null, authority); } /** * Returns the host name. * * @return the host name (IP or DNS name) */ @Override public String getHostName() { return this.host.getHostName(); } /** * Returns the port. * * @return the host port, or {@code -1} if not set */ @Override public int getPort() { return this.host.getPort(); } /** * Returns the scheme name. * * @return the scheme name */ public String getSchemeName() { return this.schemeName; } /** * Returns the inet address if explicitly set by a constructor, * {@code null} otherwise. * @return the inet address * * @since 4.3 */ public InetAddress getAddress() { return this.address; } /** * Return the host URI, as a string. * * @return the host URI */ public String toURI() { final StringBuilder buffer = new StringBuilder(); buffer.append(this.schemeName); buffer.append("://"); buffer.append(this.host.toString()); return buffer.toString(); } /** * Obtains the host string, without scheme prefix. * * @return the host string, for example {@code localhost:8080} */ public String toHostString() { return this.host.toString(); } @Override public String toString() { return toURI(); } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj instanceof HttpHost) { final HttpHost that = (HttpHost) obj; return this.schemeName.equals(that.schemeName) && this.host.equals(that.host) && Objects.equals(this.address, that.address); } return false; } /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { int hash = LangUtils.HASH_SEED; hash = LangUtils.hashCode(hash, this.schemeName); hash = LangUtils.hashCode(hash, this.host); hash = LangUtils.hashCode(hash, this.address); return hash; } } httpcore5/src/main/java/org/apache/hc/core5/http/HttpMessage.java0100644 0000000 0000000 00000007657 00000250600 023640 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * HTTP messages consist of requests from client to server and responses * from server to client. * * @since 4.0 */ public interface HttpMessage extends MessageHeaders { /** * Sets protocol version. *

* For incoming messages it represents protocol version this message was transmitted with. * For outgoing messages it represents a hint what protocol version should be used to transmit * the message. *

* * @param version The protocol version. * @since 5.0 */ void setVersion(ProtocolVersion version); /** * Returns protocol version or {@code null} when not available. *

* For incoming messages it represents protocol version this message was transmitted with. * For outgoing messages it represents a hint what protocol version should be used to transmit * the message. *

* * @return The protocol version. * @since 5.0 */ ProtocolVersion getVersion(); /** * Adds a header to this message. The header will be appended to the end of * the list. * * @param header the header to append. */ void addHeader(Header header); /** * Adds a header to this message. The header will be appended to the end of * the list. * * @param name the name of the header. * @param value the value of the header, taken as the value's {@link Object#toString()}. */ void addHeader(String name, Object value); /** * Overwrites the first header with the same name. The new header will be appended to * the end of the list, if no header with the given name can be found. * * @param header the header to set. */ void setHeader(Header header); /** * Overwrites the first header with the same name. The new header will be appended to * the end of the list, if no header with the given name can be found. * * @param name the name of the header. * @param value the value of the header, taken as the value's {@link Object#toString()}. */ void setHeader(String name, Object value); /** * Overwrites all the headers in the message. * * @param headers the array of headers to set. */ void setHeaders(Header... headers); /** * Removes a header from this message. * * @param header the header to remove. * @return {@code true} if a header was removed as a result of this call. */ boolean removeHeader(Header header); /** * Removes all headers with a certain name from this message. * * @param name The name of the headers to remove. * @return {@code true} if any header was removed as a result of this call. */ boolean removeHeaders(String name); } httpcore5/src/main/java/org/apache/hc/core5/http/HttpRequest.java0100644 0000000 0000000 00000006644 00000250600 023677 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.net.URI; import java.net.URISyntaxException; import org.apache.hc.core5.net.URIAuthority; /** * A request message from a client to a server includes, within the * first line of that message, the method to be applied to the resource, * the identifier of the resource, and the protocol version in use. * * @since 4.0 */ public interface HttpRequest extends HttpMessage { /** * Returns method of this request message. * * @return the request method. */ String getMethod(); /** * Returns URI path of this request message or {@code null} if not set. * * @return the request URI or {@code null}. */ String getPath(); /** * Sets URI path of this request message. * * @param path The URI path of this request message. * @since 5.0 */ void setPath(String path); /** * Returns scheme of this request message. * * @return the scheme or {@code null}. * @since 5.0 */ String getScheme(); /** * Sets scheme of this request message. * * @param scheme The scheme of this request message. * @since 5.0 */ void setScheme(String scheme); /** * Returns authority of this request message. * * @return the authority or {@code null}. * @since 5.0 */ URIAuthority getAuthority(); /** * Sets authority of this request message. * * @param authority The authority of this request message. * @since 5.0 */ void setAuthority(URIAuthority authority); /** * Returns request URI of this request message. It may be an absolute or relative URI. * Applicable to HTTP/1.1 version or earlier. * * @return the request URI. * @since 5.0 */ String getRequestUri(); /** * Returns full request URI of this request message. * * @return the request URI. * @throws URISyntaxException Thrown when a string could not be parsed as a URI reference. * @since 5.0 */ URI getUri() throws URISyntaxException; /** * Sets the full request URI of this request message. * * @param requestUri the request URI. * @since 5.0 */ void setUri(final URI requestUri); } httpcore5/src/main/java/org/apache/hc/core5/http/HttpRequestFactory.java0100644 0000000 0000000 00000004260 00000250600 025217 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.net.URI; /** * A factory for {@link HttpRequest} objects. * * @param The type of {@link HttpRequest}. * @since 4.0 */ public interface HttpRequestFactory { /** * Creates request message with the given request method and request URI. * * @param method the request method * @param uri the request URI * * @return request message * @throws MethodNotSupportedException if the given {@code method} is not supported. * * @since 5.0 */ T newHttpRequest(String method, String uri) throws MethodNotSupportedException; /** * Creates request message with the given request method and request URI. * * @param method the request method * @param uri the request URI * * @return request message * @throws MethodNotSupportedException if the given {@code method} is not supported. */ T newHttpRequest(String method, URI uri) throws MethodNotSupportedException; } httpcore5/src/main/java/org/apache/hc/core5/http/HttpRequestInterceptor.java0100644 0000000 0000000 00000005641 00000250600 026112 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.protocol.HttpContext; /** * HTTP protocol interceptor is a routine that implements a specific aspect of * the HTTP protocol. Usually protocol interceptors are expected to act upon * one specific header or a group of related headers of the incoming message * or populate the outgoing message with one specific header or a group of * related headers. *

* Protocol Interceptors can also manipulate content entities enclosed with messages. * Usually this is accomplished by using the 'Decorator' pattern where a wrapper * entity class is used to decorate the original entity. *

* Protocol interceptors must be implemented as thread-safe. Similarly to * servlets, protocol interceptors should not use instance variables unless * access to those variables is synchronized. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.STATELESS) public interface HttpRequestInterceptor { /** * Processes a request. * On the client side, this step is performed before the request is * sent to the server. On the server side, this step is performed * on incoming messages before the message body is evaluated. * * @param request the request to process * @param entity the request entity details or {@code null} if not available * @param context the context for the request * * @throws HttpException in case of an HTTP protocol violation * @throws IOException in case of an I/O error */ void process(HttpRequest request, EntityDetails entity, HttpContext context) throws HttpException, IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/HttpRequestMapper.java0100644 0000000 0000000 00000003570 00000250600 025037 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import org.apache.hc.core5.http.protocol.HttpContext; /** * This class can be used to resolve an object matching a particular {@link HttpRequest}. * Usually the mapped object will be a request handler to process the request. * * @param The type of HTTP request handler. * @since 5.0 */ public interface HttpRequestMapper { /** * Resolves a handler matching the given request. * * @param request the request to map to a handler * @return HTTP request handler or {@code null} if no match is found. * @throws HttpException in case of an HTTP protocol violation. */ T resolve(HttpRequest request, HttpContext context) throws HttpException; } httpcore5/src/main/java/org/apache/hc/core5/http/HttpResponse.java0100644 0000000 0000000 00000005135 00000250600 024037 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.util.Locale; /** * After receiving and interpreting a request message, a server responds * with an HTTP response message. * * @since 4.0 */ public interface HttpResponse extends HttpMessage { /** * Obtains the code of this response message. * * @return the status code. * * @see HttpStatus */ int getCode(); /** * Updates status code of this response message. * * @param code the HTTP status code. * * @see HttpStatus */ void setCode(int code); /** * Obtains the reason phrase of this response if available. * * @return the reason phrase. */ String getReasonPhrase(); /** * Updates the status line of this response with a new reason phrase. * * @param reason the new reason phrase as a single-line string, or * {@code null} to unset the reason phrase */ void setReasonPhrase(String reason); /** * Obtains the locale of this response. * The locale is used to determine the reason phrase * for the {@link #setCode status code}. * It can be changed using {@link #setLocale setLocale}. * * @return the locale of this response, never {@code null} */ Locale getLocale(); /** * Changes the locale of this response. * * @param loc the new locale */ void setLocale(Locale loc); } httpcore5/src/main/java/org/apache/hc/core5/http/HttpResponseFactory.java0100644 0000000 0000000 00000003551 00000250600 025367 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * A factory for {@link HttpResponse} objects. * * @param The type of {@link HttpResponse}. * @since 4.0 */ public interface HttpResponseFactory { /** * Creates response message with the given code and reason phrase. * * @param status the status code * @param reasonPhrase the reason phrase * * @return response message * * @since 5.0 */ T newHttpResponse(int status, String reasonPhrase); /** * Creates a new response with the given code. * * @param status the status code * * @return response message */ T newHttpResponse(int status); } httpcore5/src/main/java/org/apache/hc/core5/http/HttpResponseInterceptor.java0100644 0000000 0000000 00000005646 00000250600 026265 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.protocol.HttpContext; /** * HTTP protocol interceptor is a routine that implements a specific aspect of * the HTTP protocol. Usually protocol interceptors are expected to act upon * one specific header or a group of related headers of the incoming message * or populate the outgoing message with one specific header or a group of * related headers. *

* Protocol interceptors can also manipulate content entities enclosed with messages. * Usually this is accomplished by using the 'Decorator' pattern where a wrapper * entity class is used to decorate the original entity. *

* Protocol interceptors must be implemented as thread-safe. Similarly to * servlets, protocol interceptors should not use instance variables unless * access to those variables is synchronized. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.STATELESS) public interface HttpResponseInterceptor { /** * Processes a response. * On the server side, this step is performed before the response is * sent to the client. On the client side, this step is performed * on incoming messages before the message body is evaluated. * * @param response the response to process * @param entity the request entity details or {@code null} if not available * @param context the context for the request * * @throws HttpException in case of an HTTP protocol violation * @throws IOException in case of an I/O error */ void process(HttpResponse response, EntityDetails entity, HttpContext context) throws HttpException, IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/HttpStatus.java0100644 0000000 0000000 00000023675 00000250600 023535 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * Constants enumerating the HTTP status codes. * * @since 4.0 * @see org.apache.hc.core5.http.message.StatusLine.StatusClass * @see org.apache.hc.core5.http.message.StatusLine.StatusClass#from(int) */ public final class HttpStatus { private HttpStatus() { // no instances. } // --- 1xx Informational --- /** {@code 100 1xx Informational} (HTTP Semantics) */ public static final int SC_INFORMATIONAL = 100; /** {@code 100 Continue} (HTTP Semantics) */ public static final int SC_CONTINUE = 100; /** {@code 101 Switching Protocols} (HTTP Semantics)*/ public static final int SC_SWITCHING_PROTOCOLS = 101; /** {@code 102 Processing} (WebDAV - RFC 2518) */ public static final int SC_PROCESSING = 102; /** {@code 103 Early Hints (Early Hints - RFC 8297)}*/ public static final int SC_EARLY_HINTS = 103; // --- 2xx Success --- /** {@code 2xx Success} (HTTP Semantics) */ public static final int SC_SUCCESS = 200; /** {@code 200 OK} (HTTP Semantics) */ public static final int SC_OK = 200; /** {@code 201 Created} (HTTP Semantics) */ public static final int SC_CREATED = 201; /** {@code 202 Accepted} (HTTP Semantics) */ public static final int SC_ACCEPTED = 202; /** {@code 203 Non Authoritative Information} (HTTP Semantics) */ public static final int SC_NON_AUTHORITATIVE_INFORMATION = 203; /** {@code 204 No Content} (HTTP Semantics) */ public static final int SC_NO_CONTENT = 204; /** {@code 205 Reset Content} (HTTP Semantics) */ public static final int SC_RESET_CONTENT = 205; /** {@code 206 Partial Content} (HTTP Semantics) */ public static final int SC_PARTIAL_CONTENT = 206; /** * {@code 207 Multi-Status} (WebDAV - RFC 2518) * or * {@code 207 Partial Update OK} (HTTP/1.1 - draft-ietf-http-v11-spec-rev-01?) */ public static final int SC_MULTI_STATUS = 207; /** * {@code 208 Already Reported} (WebDAV - RFC 5842, p.30, section 7.1) */ public static final int SC_ALREADY_REPORTED = 208; /** * {@code 226 IM Used} (Delta encoding in HTTP - RFC 3229, p. 30, section 10.4.1) */ public static final int SC_IM_USED = 226; // --- 3xx Redirection --- /** {@code 3xx Redirection} (HTTP Semantics) */ public static final int SC_REDIRECTION = 300; /** {@code 300 Multiple Choices} (HTTP Semantics) */ public static final int SC_MULTIPLE_CHOICES = 300; /** {@code 301 Moved Permanently} (HTTP Semantics) */ public static final int SC_MOVED_PERMANENTLY = 301; /** {@code 302 Moved Temporarily} (Sometimes {@code Found}) (HTTP Semantics) */ public static final int SC_MOVED_TEMPORARILY = 302; /** {@code 303 See Other} (HTTP Semantics) */ public static final int SC_SEE_OTHER = 303; /** {@code 304 Not Modified} (HTTP Semantics) */ public static final int SC_NOT_MODIFIED = 304; /** {@code 305 Use Proxy} (HTTP Semantics) */ public static final int SC_USE_PROXY = 305; /** {@code 307 Temporary Redirect} (HTTP Semantics) */ public static final int SC_TEMPORARY_REDIRECT = 307; /** {@code 308 Permanent Redirect} (HTTP Semantics) */ public static final int SC_PERMANENT_REDIRECT = 308; // --- 4xx Client Error --- /** {@code 4xx Client Error} (HTTP Semantics) */ public static final int SC_CLIENT_ERROR = 400; /** {@code 400 Bad Request} (HTTP Semantics) */ public static final int SC_BAD_REQUEST = 400; /** {@code 401 Unauthorized} (HTTP Semantics) */ public static final int SC_UNAUTHORIZED = 401; /** {@code 402 Payment Required} (HTTP Semantics) */ public static final int SC_PAYMENT_REQUIRED = 402; /** {@code 403 Forbidden} (HTTP Semantics) */ public static final int SC_FORBIDDEN = 403; /** {@code 404 Not Found} (HTTP Semantics) */ public static final int SC_NOT_FOUND = 404; /** {@code 405 Method Not Allowed} (HTTP Semantics) */ public static final int SC_METHOD_NOT_ALLOWED = 405; /** {@code 406 Not Acceptable} (HTTP Semantics) */ public static final int SC_NOT_ACCEPTABLE = 406; /** {@code 407 Proxy Authentication Required} (HTTP Semantics)*/ public static final int SC_PROXY_AUTHENTICATION_REQUIRED = 407; /** {@code 408 Request Timeout} (HTTP Semantics) */ public static final int SC_REQUEST_TIMEOUT = 408; /** {@code 409 Conflict} (HTTP Semantics) */ public static final int SC_CONFLICT = 409; /** {@code 410 Gone} (HTTP Semantics) */ public static final int SC_GONE = 410; /** {@code 411 Length Required} (HTTP Semantics) */ public static final int SC_LENGTH_REQUIRED = 411; /** {@code 412 Precondition Failed} (HTTP Semantics) */ public static final int SC_PRECONDITION_FAILED = 412; /** {@code 413 Request Entity Too Large} (HTTP Semantics) */ public static final int SC_REQUEST_TOO_LONG = 413; /** {@code 414 Request-URI Too Long} (HTTP Semantics) */ public static final int SC_REQUEST_URI_TOO_LONG = 414; /** {@code 415 Unsupported Media Type} (HTTP Semantics) */ public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415; /** {@code 416 Requested Range Not Satisfiable} (HTTP Semantics) */ public static final int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416; /** {@code 417 Expectation Failed} (HTTP Semantics) */ public static final int SC_EXPECTATION_FAILED = 417; /** {@code 421 Misdirected Request} (HTTP Semantics) */ public static final int SC_MISDIRECTED_REQUEST = 421; /** {@code 422 Unprocessable Content} (HTTP Semantics) */ public static final int SC_UNPROCESSABLE_CONTENT = 422; /** {@code 426 Upgrade Required} (HTTP Semantics) */ public static final int SC_UPGRADE_REQUIRED = 426; /** * Static constant for a 419 error. * {@code 419 Insufficient Space on Resource} * (WebDAV - draft-ietf-webdav-protocol-05?) * or {@code 419 Proxy Reauthentication Required} * (HTTP/1.1 drafts?) */ public static final int SC_INSUFFICIENT_SPACE_ON_RESOURCE = 419; /** * Static constant for a 420 error. * {@code 420 Method Failure} * (WebDAV - draft-ietf-webdav-protocol-05?) */ public static final int SC_METHOD_FAILURE = 420; /** * @deprecated Use {@link #SC_UNPROCESSABLE_CONTENT} */ @Deprecated public static final int SC_UNPROCESSABLE_ENTITY = SC_UNPROCESSABLE_CONTENT; /** {@code 423 Locked} (WebDAV - RFC 2518) */ public static final int SC_LOCKED = 423; /** {@code 424 Failed Dependency} (WebDAV - RFC 2518) */ public static final int SC_FAILED_DEPENDENCY = 424; /** {@code 425 Too Early} (Using Early Data in HTTP - RFC 8470) */ public static final int SC_TOO_EARLY = 425; /** {@code 428 Precondition Required} (Additional HTTP Status Codes - RFC 6585) */ public static final int SC_PRECONDITION_REQUIRED = 428; /** {@code 429 Too Many Requests} (Additional HTTP Status Codes - RFC 6585) */ public static final int SC_TOO_MANY_REQUESTS = 429; /** {@code 431 Request Header Fields Too Large} (Additional HTTP Status Codes - RFC 6585) */ public static final int SC_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; /** {@code 451 Unavailable For Legal Reasons} (Legal Obstacles - RFC 7725) */ public static final int SC_UNAVAILABLE_FOR_LEGAL_REASONS = 451; // --- 5xx Server Error --- /** {@code 500 Server Error} (HTTP Semantics) */ public static final int SC_SERVER_ERROR = 500; /** {@code 500 Internal Server Error} (HTTP Semantics) */ public static final int SC_INTERNAL_SERVER_ERROR = 500; /** {@code 501 Not Implemented} (HTTP Semantics) */ public static final int SC_NOT_IMPLEMENTED = 501; /** {@code 502 Bad Gateway} (HTTP Semantics) */ public static final int SC_BAD_GATEWAY = 502; /** {@code 503 Service Unavailable} (HTTP Semantics) */ public static final int SC_SERVICE_UNAVAILABLE = 503; /** {@code 504 Gateway Timeout} (HTTP Semantics) */ public static final int SC_GATEWAY_TIMEOUT = 504; /** {@code 505 HTTP Version Not Supported} (HTTP Semantics) */ public static final int SC_HTTP_VERSION_NOT_SUPPORTED = 505; /** {@code 506 Variant Also Negotiates} ( Transparent Content Negotiation - RFC 2295) */ public static final int SC_VARIANT_ALSO_NEGOTIATES = 506; /** {@code 507 Insufficient Storage} (WebDAV - RFC 2518) */ public static final int SC_INSUFFICIENT_STORAGE = 507; /** * {@code 508 Loop Detected} (WebDAV - RFC 5842, p.33, section 7.2) */ public static final int SC_LOOP_DETECTED = 508; /** * {@code 510 Not Extended} (An HTTP Extension Framework - RFC 2774, p. 10, section 7) */ public static final int SC_NOT_EXTENDED = 510; /** {@code 511 Network Authentication Required} (Additional HTTP Status Codes - RFC 6585) */ public static final int SC_NETWORK_AUTHENTICATION_REQUIRED = 511; } httpcore5/src/main/java/org/apache/hc/core5/http/HttpStreamResetException.java0100644 0000000 0000000 00000003226 00000250600 026355 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.IOException; /** * Signals HTTP protocol error that renders the actual HTTP data stream unreliable. * * @since 5.0 */ public class HttpStreamResetException extends IOException { private static final long serialVersionUID = 1L; public HttpStreamResetException(final String message) { super(message); } public HttpStreamResetException(final String message, final Throwable cause) { super(message, cause); } } httpcore5/src/main/java/org/apache/hc/core5/http/HttpVersion.java0100644 0000000 0000000 00000007315 00000250600 023670 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; /** * Represents an HTTP version. HTTP uses a "major.minor" numbering * scheme to indicate versions of the protocol. *

* The version of an HTTP message is indicated by an HTTP-Version field * in the first line of the message. *

* * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public final class HttpVersion extends ProtocolVersion { private static final long serialVersionUID = -5856653513894415344L; /** The protocol name. */ public static final String HTTP = "HTTP"; /** HTTP protocol version 0.9 */ public static final HttpVersion HTTP_0_9 = new HttpVersion(0, 9); /** HTTP protocol version 1.0 */ public static final HttpVersion HTTP_1_0 = new HttpVersion(1, 0); /** HTTP protocol version 1.1 */ public static final HttpVersion HTTP_1_1 = new HttpVersion(1, 1); /** HTTP protocol version 2.0 */ public static final HttpVersion HTTP_2_0 = new HttpVersion(2, 0); public static final HttpVersion HTTP_2 = HTTP_2_0; /** HTTP/1.1 is default */ public static final HttpVersion DEFAULT = HTTP_1_1; /** * All HTTP versions known to HttpCore. */ public static final HttpVersion[] ALL = {HTTP_0_9, HTTP_1_0, HTTP_1_1, HTTP_2_0}; /** * Gets a specific instance or creates a new one. * * @param major the major version * @param minor the minor version * * @return an instance of {@link HttpVersion} with the argument version, never null. * @throws IllegalArgumentException if either major or minor version number is negative * @since 5.0 */ public static HttpVersion get(final int major, final int minor) { if (major == 1) { if (minor == 1) { return HTTP_1_1; } else if (minor == 0) { return HTTP_1_0; } } else if (major == 2 && minor == 0) { return HTTP_2_0; } // argument checking is done in the constructor return new HttpVersion(major, minor); } /** * Creates an HTTP protocol version designator. * * @param major the major version number of the HTTP protocol * @param minor the minor version number of the HTTP protocol * * @throws IllegalArgumentException if either major or minor version number is negative */ public HttpVersion(final int major, final int minor) { super(HTTP, major, minor); } } httpcore5/src/main/java/org/apache/hc/core5/http/LengthRequiredException.java0100644 0000000 0000000 00000003446 00000250600 026205 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * Signals message rejection due to missing content length. * * @since 4.0 */ public class LengthRequiredException extends ProtocolException { private static final long serialVersionUID = 1049109801075840707L; /** * Creates an exception without a default detail message. */ public LengthRequiredException() { super("Length required"); } /** * Creates an exception with the specified detail message. * * @param message The exception detail message */ public LengthRequiredException(final String message) { super(message); } } httpcore5/src/main/java/org/apache/hc/core5/http/MalformedChunkCodingException.java0100644 0000000 0000000 00000004477 00000250600 027313 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.IOException; /** * Signals a malformed chunked stream. * * @since 4.0 */ public class MalformedChunkCodingException extends IOException { private static final long serialVersionUID = 2158560246948994524L; /** * Creates a MalformedChunkCodingException without a detail message. */ public MalformedChunkCodingException() { super(); } /** * Creates a MalformedChunkCodingException with the specified detail message. * * @param message The exception detail message */ public MalformedChunkCodingException(final String message) { super(message); } /** * Constructs a new MalformedChunkCodingException with the specified detail message. * * @param format The exception detail message format; see {@link String#format(String, Object...)}. * @param args The exception detail message arguments; see {@link String#format(String, Object...)}. * * @since 5.0 */ public MalformedChunkCodingException(final String format, final Object... args) { super(HttpException.clean(String.format(format, args))); } } httpcore5/src/main/java/org/apache/hc/core5/http/Message.java0100644 0000000 0000000 00000004503 00000250600 022763 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import org.apache.hc.core5.util.Args; /** * Generic message consisting of a message head and a message body. * * @param message head type. * @param message body type. * * @since 5.0 */ public final class Message { private final H head; private final B body; /** * Constructs a new instance. * * @param head The message head. * @since 5.3 */ public Message(final H head) { this(head, null); } /** * Constructs a new instance. * * @param head The message head. * @param body The message body. */ public Message(final H head, final B body) { this.head = Args.notNull(head, "Message head"); this.body = body; } /** * Gets the message head. * * @return the message head. */ public H getHead() { return head; } /** * Gets the message body. * * @return the message body. */ public B getBody() { return body; } @Override public String toString() { return "[head=" + head + ", body=" + body + ']'; } } httpcore5/src/main/java/org/apache/hc/core5/http/MessageConstraintException.java0100644 0000000 0000000 00000003272 00000250600 026711 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.IOException; /** * Signals a message constraint violation. * * @since 4.3 */ public class MessageConstraintException extends IOException { private static final long serialVersionUID = 6077207720446368695L; /** * Creates a MessageConstraintException with the specified detail message. * * @param message The exception detail message */ public MessageConstraintException(final String message) { super(HttpException.clean(message)); } } httpcore5/src/main/java/org/apache/hc/core5/http/MessageHeaders.java0100644 0000000 0000000 00000011033 00000250600 024253 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.util.Iterator; /** * Messages head consisting of multiple message headers. * * @since 5.0 */ public interface MessageHeaders { /** * Checks if a certain header is present in this message. Header values are * ignored. * * @param name the header name to check for. * @return true if at least one header with this name is present. */ boolean containsHeader(String name); /** * Checks if a certain header is present in this message and how many times. * * @param name the header name to check for. * @return number of occurrences of the header in the message. */ int countHeaders(String name); /** * Returns the first header with a specified name of this message. Header * values are ignored. If there is more than one matching header in the * message the first element of {@link #getHeaders(String)} is returned. * If there is no matching header in the message {@code null} is * returned. * * @param name the name of the header to return. * @return the first header whose name property equals {@code name} * or {@code null} if no such header could be found. */ Header getFirstHeader(String name); /** * Gets single first header with the given name. * *

Header name comparison is case insensitive. * * @param name the name of the header to get * @return the first header or {@code null} * @throws ProtocolException in case multiple headers with the given name are found. */ Header getHeader(String name) throws ProtocolException; /** * Returns all the headers of this message. Headers are ordered in the sequence * they will be sent over a connection. * * @return all the headers of this message */ Header[] getHeaders(); /** * Returns all the headers with a specified name of this message. Header values * are ignored. Headers are ordered in the sequence they will be sent over a * connection. * * @param name the name of the headers to return. * @return the headers whose name property equals {@code name}. */ Header[] getHeaders(String name); /** * Returns the last header with a specified name of this message. Header values * are ignored. If there is more than one matching header in the message the * last element of {@link #getHeaders(String)} is returned. If there is no * matching header in the message {@code null} is returned. * * @param name the name of the header to return. * @return the last header whose name property equals {@code name}. * or {@code null} if no such header could be found. */ Header getLastHeader(String name); /** * Returns an iterator of all the headers. * * @return Iterator that returns Header objects in the sequence they are * sent over a connection. */ Iterator

headerIterator(); /** * Returns an iterator of the headers with a given name. * * @param name the name of the headers over which to iterate, or * {@code null} for all headers * * @return Iterator that returns Header objects with the argument name * in the sequence they are sent over a connection. */ Iterator
headerIterator(String name); } httpcore5/src/main/java/org/apache/hc/core5/http/Method.java0100644 0000000 0000000 00000012272 00000250600 022621 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.util.Locale; import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.util.Args; /** * Common HTTP methods defined by the HTTP specification. *

* Each method is: *

*
    *
  • Either safe or unsafe: An HTTP method is safe if it doesn't change the state of the server. In other words, a method is safe if it's * read-only.
  • *
  • Either idempotent or non-idempotent: An HTTP method is idempotent if making a single request has the same effect as making several * identical requests. All safe methods are also idempotent, but not all idempotent methods are safe. For example, {@code PUT} and {@code DELETE} are both * idempotent but unsafe.
  • *
* * @since 5.0 */ public enum Method { /** * The HTTP {@code GET} method is safe and idempotent. */ GET(true, true), /** * The HTTP {@code HEAD} method is safe and idempotent. */ HEAD(true, true), /** * The HTTP {@code POST} method is unsafe and non-idempotent. */ POST(false, false), /** * The HTTP {@code PUT} method is unsafe and idempotent. */ PUT(false, true), /** * The HTTP {@code DELETE} method is unsafe and idempotent. */ DELETE(false, true), /** * The HTTP {@code CONNECT} method is unsafe and non-idempotent. */ CONNECT(false, false), /** * The HTTP {@code TRACE} method is safe and idempotent. */ TRACE(true, true), /** * The HTTP {@code OPTIONS} method is safe and idempotent. */ OPTIONS(true, true), /** * The HTTP {@code PATCH} method is unsafe and non-idempotent. */ PATCH(false, false), /** * The HTTP {@code QUERY} method is safe and idempotent. *

* {@code QUERY} is a method defined to represent a safe, idempotent request that carries a body. * This allows clients to send a body while enjoying the {@code GET}-like properties (such as easy caching, * safe CORS handling [if supported] and bookmarking), as well as the {@code POST}-like properties (such as * being able to send a query in a richer format, and not being limited by URI length and escaping restrictions). * * @since 5.4 */ @Experimental //("QUERY method is still in DRAFT status") QUERY(true, true); private final boolean safe; private final boolean idempotent; Method(final boolean safe, final boolean idempotent) { this.safe = safe; this.idempotent = idempotent; } /** * Tests whether this method is safe. * * @return whether this method is safe. */ public boolean isSafe() { return safe; } /** * Tests whether this method is idempotent. * * @return whether this method is idempotent. */ public boolean isIdempotent() { return idempotent; } public static boolean isSafe(final String value) { if (value == null) { return false; } try { return normalizedValueOf(value).safe; } catch (final IllegalArgumentException ex) { return false; } } public static boolean isIdempotent(final String value) { if (value == null) { return false; } try { return normalizedValueOf(value).idempotent; } catch (final IllegalArgumentException ex) { return false; } } /** * Returns the Method for a normalized {@code value} of a method name. * * @param method A method name like {@code "delete"}, {@code "DELETE"}, or any mixed-case variant. * @return the Method for the given method name. */ public static Method normalizedValueOf(final String method) { return valueOf(Args.notNull(method, "method").toUpperCase(Locale.ROOT)); } public boolean isSame(final String value) { if (value == null) { return false; } return name().equalsIgnoreCase(value); } } httpcore5/src/main/java/org/apache/hc/core5/http/MethodNotSupportedException.java0100644 0000000 0000000 00000004111 00000250600 027060 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * Signals that an HTTP method is not supported. * * @since 4.0 */ public class MethodNotSupportedException extends ProtocolException { private static final long serialVersionUID = 1L; /** * Creates a new MethodNotSupportedException with the specified detail message. * * @param message The exception detail message */ public MethodNotSupportedException(final String message) { super(message); } /** * Creates a new MethodNotSupportedException with the specified detail message and cause. * * @param message the exception detail message * @param cause the {@code Throwable} that caused this exception, or {@code null} * if the cause is unavailable, unknown, or not a {@code Throwable} */ public MethodNotSupportedException(final String message, final Throwable cause) { super(message, cause); } } httpcore5/src/main/java/org/apache/hc/core5/http/MisdirectedRequestException.java0100644 0000000 0000000 00000003445 00000250600 027067 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * Signals a misdirected request (the server is not authoritative to handle the request). * * @since 5.0 */ public class MisdirectedRequestException extends ProtocolException { private static final long serialVersionUID = 1L; /** * Creates an exception without a detail message. */ public MisdirectedRequestException() { super(); } /** * Creates an exception with the specified detail message. * * @param message The exception detail message */ public MisdirectedRequestException(final String message) { super(message); } } httpcore5/src/main/java/org/apache/hc/core5/http/NameValuePair.java0100644 0000000 0000000 00000003137 00000250600 024072 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * A name-value pair parameter used as an element of HTTP messages. * * @since 4.0 */ public interface NameValuePair { /** * Gets the name of this pair. * * @return the name of this pair, never {@code null}. */ String getName(); /** * Gets the value of this pair. * * @return the value of this pair, may be {@code null}. */ String getValue(); } httpcore5/src/main/java/org/apache/hc/core5/http/NoHttpResponseException.java0100644 0000000 0000000 00000004062 00000250600 026211 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.IOException; /** * Signals that the target server failed to respond with a valid HTTP response. * * @since 4.0 */ public class NoHttpResponseException extends IOException { private static final long serialVersionUID = -7658940387386078766L; /** * Creates a new NoHttpResponseException with the specified detail message. * * @param message exception message */ public NoHttpResponseException(final String message) { super(HttpException.clean(message)); } /** * Constructs a {@code NoHttpResponseException} with the specified detail message * and cause. * * @param message The exception detail message * @param cause The cause. * * @since 5.0 */ public NoHttpResponseException(final String message, final Throwable cause) { super(message, cause); } } httpcore5/src/main/java/org/apache/hc/core5/http/NotImplementedException.java0100644 0000000 0000000 00000003431 00000250600 026201 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * Signals an unsupported / unimplemented feature of the HTTP protocol. * * @since 4.0 */ public class NotImplementedException extends ProtocolException { private static final long serialVersionUID = 7929295893253266373L; /** * Creates an exception without a detail message. */ public NotImplementedException() { super(); } /** * Creates an exception with the specified detail message. * * @param message The exception detail message */ public NotImplementedException(final String message) { super(message); } } httpcore5/src/main/java/org/apache/hc/core5/http/ParseException.java0100644 0000000 0000000 00000005377 00000250600 024342 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * Signals a protocol exception due to failure to parse a message element. * * @since 4.0 */ public class ParseException extends ProtocolException { private static final long serialVersionUID = -7288819855864183578L; private final int errorOffset; /** * Constructs a new {@link ParseException} without details. */ public ParseException() { super(); this.errorOffset = -1; } /** * Constructs a new {@link ParseException} with a detail message. * * @param message the exception detail message, or {@code null} */ public ParseException(final String message) { super(message); this.errorOffset = -1; } /** * Constructs a new {@link ParseException} with parsing context details. * * @since 5.0 */ public ParseException(final String description, final CharSequence text, final int off, final int len, final int errorOffset) { super(description + (errorOffset >= 0 ? "; error at offset " + errorOffset : "") + (text != null && len < 1024 ? ": <" + text.subSequence(off, off + len) + ">" : "")); this.errorOffset = errorOffset; } /** * Constructs a new {@link ParseException} with parsing context details. * * @since 5.0 */ public ParseException(final String description, final CharSequence text, final int off, final int len) { this(description, text, off, len, -1); } /** * @since 5.0 */ public int getErrorOffset() { return errorOffset; } } httpcore5/src/main/java/org/apache/hc/core5/http/ProtocolException.java0100644 0000000 0000000 00000005316 00000250600 025062 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * Signals that an HTTP protocol violation has occurred. * For example a malformed status line or headers, a missing message body, etc. * * @since 4.0 */ public class ProtocolException extends HttpException { private static final long serialVersionUID = -2143571074341228994L; /** * Creates a new ProtocolException with a {@code null} detail message. */ public ProtocolException() { super(); } /** * Creates a new ProtocolException with the specified detail message. * * @param message The exception detail message */ public ProtocolException(final String message) { super(message); } /** * Constructs a new ProtocolException with the specified detail message. * * @param format The exception detail message format; see {@link String#format(String, Object...)}. * @param args The exception detail message arguments; see {@link String#format(String, Object...)}. * * @since 5.0 */ public ProtocolException(final String format, final Object... args) { super(format, args); } /** * Creates a new ProtocolException with the specified detail message and cause. * * @param message the exception detail message * @param cause the {@code Throwable} that caused this exception, or {@code null} * if the cause is unavailable, unknown, or not a {@code Throwable} */ public ProtocolException(final String message, final Throwable cause) { super(message, cause); } } httpcore5/src/main/java/org/apache/hc/core5/http/ProtocolVersion.java0100644 0000000 0000000 00000022417 00000250600 024552 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.Serializable; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Tokenizer; /** * Represents a protocol version. The "major.minor" numbering * scheme is used to indicate versions of the protocol. *

* This class defines a protocol version as a combination of * protocol name, major version number, and minor version number. * Note that {@link #equals} and {@link #hashCode} are defined as * final here, they cannot be overridden in derived classes. *

* * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class ProtocolVersion implements Serializable { private static final long serialVersionUID = 8950662842175091068L; /** Name of the protocol. */ private final String protocol; /** Major version number of the protocol */ private final int major; /** Minor version number of the protocol */ private final int minor; /** * Create a protocol version designator. * * @param protocol the name of the protocol, for example "HTTP" * @param major the major version number of the protocol * @param minor the minor version number of the protocol */ public ProtocolVersion(final String protocol, final int major, final int minor) { this.protocol = Args.notNull(protocol, "Protocol name"); this.major = Args.notNegative(major, "Protocol major version"); this.minor = Args.notNegative(minor, "Protocol minor version"); } /** * Returns the name of the protocol. * * @return the protocol name */ public final String getProtocol() { return protocol; } /** * Returns the major version number of the protocol. * * @return the major version number. */ public final int getMajor() { return major; } /** * Returns the minor version number of the HTTP protocol. * * @return the minor version number. */ public final int getMinor() { return minor; } /** * Obtains a hash code consistent with {@link #equals}. * * @return the hashcode of this protocol version */ @Override public final int hashCode() { return this.protocol.hashCode() ^ (this.major * 100000) ^ this.minor; } /** * Checks whether this instance has the same major and minor version as the arguments. * * @param major the major version to check. * @param minor the minor version to check. * @return whether this instance has the same major and minor version as the arguments. * @since 5.0 */ public final boolean equals(final int major, final int minor) { return this.major == major && this.minor == minor; } /** * Checks equality of this protocol version with an object. * The object is equal if it is a protocol version with the same * protocol name, major version number, and minor version number. * The specific class of the object is not relevant, * instances of derived classes with identical attributes are * equal to instances of the base class and vice versa. * * @param obj the object to compare with * * @return {@code true} if the argument is the same protocol version, * {@code false} otherwise */ @Override public final boolean equals(final Object obj) { if (this == obj) { return true; } if (!(obj instanceof ProtocolVersion)) { return false; } final ProtocolVersion that = (ProtocolVersion) obj; return this.protocol.equals(that.protocol) && this.major == that.major && this.minor == that.minor; } /** * Formats this protocol version as a string. * * @return a protocol version string, like "HTTP/1.1" * @since 5.0 */ public String format() { final StringBuilder buffer = new StringBuilder(); buffer.append(this.protocol); buffer.append('/'); buffer.append(this.major); buffer.append('.'); buffer.append(this.minor); return buffer.toString(); } /** * Checks whether this protocol can be compared to another one. * Only protocol versions with the same protocol name can be * {@link #compareToVersion compared}. * * @param that the protocol version to consider * * @return {@code true} if {@link #compareToVersion compareToVersion} * can be called with the argument, {@code false} otherwise */ public boolean isComparable(final ProtocolVersion that) { return that != null && this.protocol.equals(that.protocol); } /** * Compares this protocol version with another one. * Only protocol versions with the same protocol name can be compared. * This method does not define a total ordering, as it would be * required for {@link java.lang.Comparable}. * * @param that the protocol version to compare with * * @return a negative integer, zero, or a positive integer * as this version is less than, equal to, or greater than * the argument version. * * @throws IllegalArgumentException * if the argument has a different protocol name than this object, * or if the argument is {@code null} */ public int compareToVersion(final ProtocolVersion that) { Args.notNull(that, "Protocol version"); Args.check(this.protocol.equals(that.protocol), "Versions for different protocols cannot be compared: %s %s", this, that); int delta = getMajor() - that.getMajor(); if (delta == 0) { delta = getMinor() - that.getMinor(); } return delta; } /** * Tests if this protocol version is greater or equal to the given one. * * @param version the version against which to check this version * * @return {@code true} if this protocol version is * {@link #isComparable comparable} to the argument * and {@link #compareToVersion compares} as greater or equal, * {@code false} otherwise */ public final boolean greaterEquals(final ProtocolVersion version) { return isComparable(version) && compareToVersion(version) >= 0; } /** * Tests if this protocol version is less or equal to the given one. * * @param version the version against which to check this version * * @return {@code true} if this protocol version is * {@link #isComparable comparable} to the argument * and {@link #compareToVersion compares} as less or equal, * {@code false} otherwise */ public final boolean lessEquals(final ProtocolVersion version) { return isComparable(version) && compareToVersion(version) <= 0; } @Internal public static ProtocolVersion parse( final CharSequence buffer, final Tokenizer.Cursor cursor, final Tokenizer.Delimiter delimiterPredicate) throws ParseException { return ProtocolVersionParser.INSTANCE.parse(buffer, cursor, delimiterPredicate); } /** * @since 5.3 */ public static ProtocolVersion parse(final String s) throws ParseException { if (s == null) { return null; } final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); final ProtocolVersion protocolVersion = ProtocolVersionParser.INSTANCE.parse(s, cursor, null); Tokenizer.INSTANCE.skipWhiteSpace(s, cursor); if (!cursor.atEnd()) { throw new ParseException("Invalid protocol version; trailing content"); } return protocolVersion; } /** * Converts this protocol version to a string. * * @return a protocol version string, like "HTTP/1.1" */ @Override public String toString() { return format(); } } httpcore5/src/main/java/org/apache/hc/core5/http/ProtocolVersionParser.java0100644 0000000 0000000 00000012327 00000250600 025726 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.util.Tokenizer; @Internal public class ProtocolVersionParser { public final static ProtocolVersionParser INSTANCE = new ProtocolVersionParser(); private static final char SLASH = '/'; private static final char FULL_STOP = '.'; private static final Tokenizer.Delimiter PROTO_DELIMITER = Tokenizer.delimiters(SLASH); private static final Tokenizer.Delimiter FULL_STOP_OR_BLANK = Tokenizer.delimiters(FULL_STOP, ' ', '\t'); private static final Tokenizer.Delimiter BLANK = Tokenizer.delimiters(' ', '\t'); private final Tokenizer tokenizer; public ProtocolVersionParser() { this.tokenizer = Tokenizer.INSTANCE; } @Internal @FunctionalInterface public interface Factory { ProtocolVersion create(int major, int minor); } public ProtocolVersion parse( final String protocol, final Factory factory, final CharSequence buffer, final Tokenizer.Cursor cursor, final Tokenizer.Delimiter delimiterPredicate) throws ParseException { final int lowerBound = cursor.getLowerBound(); final int upperBound = cursor.getUpperBound(); final String token1 = tokenizer.parseToken(buffer, cursor, delimiterPredicate != null ? ch -> delimiterPredicate.test(ch) || FULL_STOP_OR_BLANK.test(ch) : FULL_STOP_OR_BLANK); final int major; try { major = Integer.parseInt(token1); } catch (final NumberFormatException e) { throw new ParseException("Invalid " + protocol + " major version number", buffer, lowerBound, upperBound, cursor.getPos()); } if (cursor.atEnd()) { return factory != null ? factory.create(major, major) : new ProtocolVersion(protocol, major, 0); } if (buffer.charAt(cursor.getPos()) != FULL_STOP) { return factory != null ? factory.create(major, major) : new ProtocolVersion(protocol, major, 0); } else { cursor.updatePos(cursor.getPos() + 1); final String token2 = tokenizer.parseToken(buffer, cursor, delimiterPredicate != null ? ch -> delimiterPredicate.test(ch) || BLANK.test(ch) : BLANK); final int minor; try { minor = Integer.parseInt(token2); } catch (final NumberFormatException e) { throw new ParseException("Invalid " + protocol + " minor version number", buffer, lowerBound, upperBound, cursor.getPos()); } return factory != null ? factory.create(major, minor) : new ProtocolVersion(protocol, major, minor); } } public ProtocolVersion parse( final String protocol, final CharSequence buffer, final Tokenizer.Cursor cursor, final Tokenizer.Delimiter delimiterPredicate) throws ParseException { return parse(protocol, null, buffer, cursor, delimiterPredicate); } public ProtocolVersion parse( final String protocol, final CharSequence buffer, final Tokenizer.Cursor cursor) throws ParseException { return parse(protocol, null, buffer, cursor, null); } public ProtocolVersion parse( final CharSequence buffer, final Tokenizer.Cursor cursor, final Tokenizer.Delimiter delimiterPredicate) throws ParseException { tokenizer.skipWhiteSpace(buffer, cursor); final String proto = tokenizer.parseToken(buffer, cursor, PROTO_DELIMITER); if (TextUtils.isBlank(proto)) { throw new ParseException("Invalid protocol name"); } if (!cursor.atEnd() && buffer.charAt(cursor.getPos()) == SLASH) { cursor.updatePos(cursor.getPos() + 1); return parse(proto, null, buffer, cursor, delimiterPredicate); } else { throw new ParseException("Invalid protocol name"); } } } httpcore5/src/main/java/org/apache/hc/core5/http/ReasonPhraseCatalog.java0100644 0000000 0000000 00000003411 00000250600 025261 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.util.Locale; /** * Interface for obtaining reason phrases for HTTP status codes. * * @since 4.0 */ public interface ReasonPhraseCatalog { /** * Obtains the reason phrase for a status code. * The optional context allows for catalogs that detect * the language for the reason phrase. * * @param status the status code, in the range 100-599 * @param loc the preferred locale for the reason phrase * * @return the reason phrase, or {@code null} if unknown */ String getReason(int status, Locale loc); } httpcore5/src/main/java/org/apache/hc/core5/http/RequestHeaderFieldsTooLargeException.java0100644 0000000 0000000 00000004213 00000250600 030601 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * Signals request header field length or total field size violation. * * @since 5.0 */ public class RequestHeaderFieldsTooLargeException extends ProtocolException { private static final long serialVersionUID = 1L; /** * Creates a new RequestHeaderFieldsTooLargeException with the specified detail message. * * @param message The exception detail message */ public RequestHeaderFieldsTooLargeException(final String message) { super(message); } /** * Creates a new RequestHeaderFieldsTooLargeException with the specified detail message and cause. * * @param message the exception detail message * @param cause the {@code Throwable} that caused this exception, or {@code null} * if the cause is unavailable, unknown, or not a {@code Throwable} */ public RequestHeaderFieldsTooLargeException(final String message, final Throwable cause) { super(message, cause); } } httpcore5/src/main/java/org/apache/hc/core5/http/RequestNotExecutedException.java0100644 0000000 0000000 00000003732 00000250600 027061 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import org.apache.hc.core5.annotation.Internal; /** * {@link ConnectionClosedException} subclass that signals requests cannot not be executed * due to the connection being closed. Generally requests failed with this exception * are safe to be re-executed. */ @Internal public class RequestNotExecutedException extends ConnectionClosedException { /** * Constructs a new RequestNotExecutedException with a default message. */ public RequestNotExecutedException() { super("Connection is closed"); } /** * Constructs a new RequestNotExecutedException with the specified detail message. * * @param message The exception detail message */ public RequestNotExecutedException(final String message) { super(message); } } httpcore5/src/main/java/org/apache/hc/core5/http/SocketModalCloseable.java0100644 0000000 0000000 00000003304 00000250600 025414 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import org.apache.hc.core5.io.ModalCloseable; import org.apache.hc.core5.util.Timeout; /** * A generic {@link ModalCloseable} backed by a network socket. * * @since 5.0 */ public interface SocketModalCloseable extends ModalCloseable { /** * Returns the socket timeout value. * * @return timeout value. */ Timeout getSocketTimeout(); /** * Sets the socket timeout value. * * @param timeout timeout value */ void setSocketTimeout(Timeout timeout); } httpcore5/src/main/java/org/apache/hc/core5/http/StreamClosedException.java0100644 0000000 0000000 00000003115 00000250600 025641 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.IOException; /** * Signals that data stream has already been closed. * * @since 5.0 */ public class StreamClosedException extends IOException { private static final long serialVersionUID = 1L; public StreamClosedException() { super("Stream already closed"); } public StreamClosedException(final String message) { super(message); } } httpcore5/src/main/java/org/apache/hc/core5/http/TruncatedChunkException.java0100644 0000000 0000000 00000004163 00000250600 026202 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * Signals a truncated chunk in a chunked stream. * * @since 4.1 */ public class TruncatedChunkException extends MalformedChunkCodingException { private static final long serialVersionUID = -23506263930279460L; /** * Creates a TruncatedChunkException with the specified detail message. * * @param message The exception detail message */ public TruncatedChunkException(final String message) { super(HttpException.clean(message)); } /** * Constructs a new TruncatedChunkException with the specified detail message. * * @param format The exception detail message format; see {@link String#format(String, Object...)}. * @param args The exception detail message arguments; see {@link String#format(String, Object...)}. * * @since 5.0 */ public TruncatedChunkException(final String format, final Object... args) { super(format, args); } } httpcore5/src/main/java/org/apache/hc/core5/http/URIScheme.java0100644 0000000 0000000 00000003266 00000250600 023170 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import org.apache.hc.core5.util.Args; /** * Enumerates supported URI schemes. * * @since 5.0 */ public enum URIScheme { HTTP("http"), HTTPS("https"); public final String id; URIScheme(final String id) { this.id = Args.notBlank(id, "id"); } public String getId() { return id; } public boolean same(final String scheme) { return id.equalsIgnoreCase(scheme); } @Override public String toString() { return id; } } httpcore5/src/main/java/org/apache/hc/core5/http/UnsupportedHttpVersionException.java0100644 0000000 0000000 00000004136 00000250600 030016 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; /** * Signals an unsupported version of the HTTP protocol. * * @since 4.0 */ public class UnsupportedHttpVersionException extends ProtocolException { private static final long serialVersionUID = -1348448090193107031L; /** * Creates an exception without a detail message. */ public UnsupportedHttpVersionException() { super(); } /** * Creates an exception with a detail message for the given ProtocolVersion. * * @param protocolVersion The unsupported ProtocolVersion. */ public UnsupportedHttpVersionException(final ProtocolVersion protocolVersion) { super("Unsupported version: " + protocolVersion); } /** * Creates an exception with the specified detail message. * * @param message The exception detail message */ public UnsupportedHttpVersionException(final String message) { super(message); } } httpcore5/src/main/java/org/apache/hc/core5/http/package-info.java0100644 0000000 0000000 00000003572 00000250600 023730 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Core HTTP transport component APIs. *

* These deal with the fundamental things required for using the * HTTP protocol, such as representing a * {@link org.apache.hc.core5.http.HttpMessage message} including it's * {@link org.apache.hc.core5.http.Header headers} and optional * {@link org.apache.hc.core5.http.HttpEntity entity}, and * {@link org.apache.hc.core5.http.HttpConnection connections} * over which messages are sent. In order to prepare messages * before sending or after receiving, there are interceptors for * {@link org.apache.hc.core5.http.HttpRequestInterceptor requests} and * {@link org.apache.hc.core5.http.HttpResponseInterceptor responses}. *

*/ package org.apache.hc.core5.http; httpcore5/src/main/java/org/apache/hc/core5/http/config/0040755 0000000 0000000 00000000000 00000250600 022002 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/config/CharCodingConfig.java0100644 0000000 0000000 00000011255 00000250600 025775 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.config; import java.nio.charset.Charset; import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.util.Args; /** * HTTP/1.1 char coding configuration. * * @since 4.3 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class CharCodingConfig { public static final CharCodingConfig DEFAULT = new Builder().build(); private static final Charset DEFAULT_CHARSET = StandardCharsets.US_ASCII; private final Charset charset; private final CodingErrorAction malformedInputAction; private final CodingErrorAction unmappableInputAction; CharCodingConfig( final Charset charset, final CodingErrorAction malformedInputAction, final CodingErrorAction unmappableInputAction) { super(); this.charset = charset; this.malformedInputAction = malformedInputAction; this.unmappableInputAction = unmappableInputAction; } public Charset getCharset() { return charset; } public CodingErrorAction getMalformedInputAction() { return malformedInputAction; } public CodingErrorAction getUnmappableInputAction() { return unmappableInputAction; } @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("[charset=").append(this.charset) .append(", malformedInputAction=").append(this.malformedInputAction) .append(", unmappableInputAction=").append(this.unmappableInputAction) .append("]"); return builder.toString(); } public static CharCodingConfig.Builder custom() { return new Builder(); } public static CharCodingConfig.Builder copy(final CharCodingConfig config) { Args.notNull(config, "Config"); return new Builder() .setCharset(config.getCharset()) .setMalformedInputAction(config.getMalformedInputAction()) .setUnmappableInputAction(config.getUnmappableInputAction()); } public static class Builder { private Charset charset; private CodingErrorAction malformedInputAction; private CodingErrorAction unmappableInputAction; Builder() { } public Builder setCharset(final Charset charset) { this.charset = charset; return this; } public Builder setMalformedInputAction(final CodingErrorAction malformedInputAction) { this.malformedInputAction = malformedInputAction; if (malformedInputAction != null && this.charset == null) { this.charset = DEFAULT_CHARSET; } return this; } public Builder setUnmappableInputAction(final CodingErrorAction unmappableInputAction) { this.unmappableInputAction = unmappableInputAction; if (unmappableInputAction != null && this.charset == null) { this.charset = DEFAULT_CHARSET; } return this; } public CharCodingConfig build() { Charset charsetCopy = charset; if (charsetCopy == null && (malformedInputAction != null || unmappableInputAction != null)) { charsetCopy = DEFAULT_CHARSET; } return new CharCodingConfig(charsetCopy, malformedInputAction, unmappableInputAction); } } } httpcore5/src/main/java/org/apache/hc/core5/http/config/Http1Config.java0100644 0000000 0000000 00000020522 00000250600 024771 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.config; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; /** * HTTP/1.1 protocol parameters. *

* Please note that line length is defined in bytes and not characters. * This is only relevant however when using non-standard HTTP charsets * for protocol elements such as UTF-8. *

* * @since 4.3 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class Http1Config { public static final Http1Config DEFAULT = new Builder().build(); private final HttpVersion version; private final int bufferSize; private final int chunkSizeHint; private final Timeout waitForContinueTimeout; private final int maxLineLength; private final int maxHeaderCount; private final int maxEmptyLineCount; private final int initialWindowSize; Http1Config(final HttpVersion version, final int bufferSize, final int chunkSizeHint, final Timeout waitForContinueTimeout, final int maxLineLength, final int maxHeaderCount, final int maxEmptyLineCount, final int initialWindowSize) { super(); this.version = version; this.bufferSize = bufferSize; this.chunkSizeHint = chunkSizeHint; this.waitForContinueTimeout = waitForContinueTimeout; this.maxLineLength = maxLineLength; this.maxHeaderCount = maxHeaderCount; this.maxEmptyLineCount = maxEmptyLineCount; this.initialWindowSize = initialWindowSize; } /** * The effective protocol level expressed by the minor version of HTTP/1.x. * * @return The effective protocol level. * @since 5.3 */ public HttpVersion getVersion() { return version; } public int getBufferSize() { return bufferSize; } public int getChunkSizeHint() { return chunkSizeHint; } public Timeout getWaitForContinueTimeout() { return waitForContinueTimeout; } public int getMaxLineLength() { return maxLineLength; } public int getMaxHeaderCount() { return maxHeaderCount; } public int getMaxEmptyLineCount() { return this.maxEmptyLineCount; } public int getInitialWindowSize() { return initialWindowSize; } @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("[version=").append(version) .append(", bufferSize=").append(bufferSize) .append(", chunkSizeHint=").append(chunkSizeHint) .append(", waitForContinueTimeout=").append(waitForContinueTimeout) .append(", maxLineLength=").append(maxLineLength) .append(", maxHeaderCount=").append(maxHeaderCount) .append(", maxEmptyLineCount=").append(maxEmptyLineCount) .append(", initialWindowSize=").append(initialWindowSize) .append("]"); return builder.toString(); } public static Http1Config.Builder custom() { return new Builder(); } public static Http1Config.Builder copy(final Http1Config config) { Args.notNull(config, "Config"); return new Builder() .setVersion(config.getVersion()) .setBufferSize(config.getBufferSize()) .setChunkSizeHint(config.getChunkSizeHint()) .setWaitForContinueTimeout(config.getWaitForContinueTimeout()) .setMaxHeaderCount(config.getMaxHeaderCount()) .setMaxLineLength(config.getMaxLineLength()) .setMaxEmptyLineCount(config.getMaxEmptyLineCount()) .setInitialWindowSize(config.getInitialWindowSize()); } private static final int INIT_WINDOW_SIZE = 65535; private static final int INIT_BUF_SIZE = 8192; private static final Timeout INIT_WAIT_FOR_CONTINUE = Timeout.ofSeconds(3); private static final int INIT_BUF_CHUNK = -1; private static final int INIT_MAX_HEADER_COUNT = 100; private static final int INIT_MAX_LINE_LENGTH = 8192; private static final int INIT_MAX_EMPTY_LINE_COUNT = 10; public static class Builder { private HttpVersion version; private int bufferSize; private int chunkSizeHint; private Timeout waitForContinueTimeout; private int maxLineLength; private int maxHeaderCount; private int maxEmptyLineCount; private int initialWindowSize; Builder() { this.version = HttpVersion.HTTP_1_1; this.bufferSize = INIT_BUF_SIZE; this.chunkSizeHint = INIT_BUF_CHUNK; this.waitForContinueTimeout = INIT_WAIT_FOR_CONTINUE; this.maxLineLength = INIT_MAX_LINE_LENGTH; this.maxHeaderCount = INIT_MAX_HEADER_COUNT; this.maxEmptyLineCount = INIT_MAX_EMPTY_LINE_COUNT; this.initialWindowSize = INIT_WINDOW_SIZE; } /** * Sets the effective HTTP/1.x protocol level (as expressed by the minor version). * Presently only {@link HttpVersion#HTTP_1_0} and {@link HttpVersion#HTTP_1_1} are * supported. * * @param version the effective HTTP/1.x protocol level. * @return this instance. * @since 5.3 */ public Builder setVersion(final HttpVersion version) { Args.notNull(version, "HTTP/1 protocol version"); Args.check(version.getMajor() == 1, "HTTP/1 protocol version is required"); this.version = version; return this; } public Builder setBufferSize(final int bufferSize) { this.bufferSize = bufferSize; return this; } public Builder setChunkSizeHint(final int chunkSizeHint) { this.chunkSizeHint = chunkSizeHint; return this; } public Builder setWaitForContinueTimeout(final Timeout waitForContinueTimeout) { this.waitForContinueTimeout = waitForContinueTimeout; return this; } public Builder setMaxLineLength(final int maxLineLength) { this.maxLineLength = maxLineLength; return this; } public Builder setMaxHeaderCount(final int maxHeaderCount) { this.maxHeaderCount = maxHeaderCount; return this; } public Builder setMaxEmptyLineCount(final int maxEmptyLineCount) { this.maxEmptyLineCount = maxEmptyLineCount; return this; } public Builder setInitialWindowSize(final int initialWindowSize) { Args.positive(initialWindowSize, "Initial window size"); this.initialWindowSize = initialWindowSize; return this; } public Http1Config build() { return new Http1Config( version, bufferSize, chunkSizeHint, waitForContinueTimeout, maxLineLength, maxHeaderCount, maxEmptyLineCount, initialWindowSize); } } } httpcore5/src/main/java/org/apache/hc/core5/http/config/Lookup.java0100644 0000000 0000000 00000003014 00000250600 024111 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.config; /** * Generic lookup by lower-case string ID. * * @param the type of values to lookup. * @since 4.3 */ public interface Lookup { /** * Looks up a value using a lower-case string ID. * * @param name The lookup name. * @return The matching value. */ I lookup(String name); } httpcore5/src/main/java/org/apache/hc/core5/http/config/NamedElementChain.java0100644 0000000 0000000 00000012706 00000250600 026151 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.config; import org.apache.hc.core5.util.Args; /** * Chain of doubly linked elements. *

* This implementation makes no attempts to ensure uniqueness of element names. * * @param */ public class NamedElementChain { private final Node master; private int size; public NamedElementChain() { this.master = new Node("master", null); this.master.previous = this.master; this.master.next = this.master; this.size = 0; } public Node getFirst() { return master.next != master ? master.next : null; } public Node getLast() { return master.previous != master ? master.previous : null; } public Node addFirst(final E value, final String name) { Args.notBlank(name, "Name"); Args.notNull(value, "Value"); final Node newNode = new Node(name, value); final Node oldNode = master.next; master.next = newNode; newNode.previous = master; newNode.next = oldNode; oldNode.previous = newNode; size++; return newNode; } public Node addLast(final E value, final String name) { Args.notBlank(name, "Name"); Args.notNull(value, "Value"); final Node newNode = new Node(name, value); final Node oldNode = master.previous; master.previous = newNode; newNode.previous = oldNode; newNode.next = master; oldNode.next = newNode; size++; return newNode; } public Node find(final String name) { Args.notBlank(name, "Name"); return doFind(name); } private Node doFind(final String name) { Node current = master.next; while (current != master) { if (name.equals(current.name)) { return current; } current = current.next; } return null; } public Node addBefore(final String existing, final E value, final String name) { Args.notBlank(name, "Name"); Args.notNull(value, "Value"); final Node current = doFind(existing); if (current == null) { return null; } final Node newNode = new Node(name, value); final Node previousNode = current.previous; previousNode.next = newNode; newNode.previous = previousNode; newNode.next = current; current.previous = newNode; size++; return newNode; } public Node addAfter(final String existing, final E value, final String name) { Args.notBlank(name, "Name"); Args.notNull(value, "Value"); final Node current = doFind(existing); if (current == null) { return null; } final Node newNode = new Node(name, value); final Node nextNode = current.next; current.next = newNode; newNode.previous = current; newNode.next = nextNode; nextNode.previous = newNode; size++; return newNode; } public boolean remove(final String name) { final Node node = doFind(name); if (node == null) { return false; } node.previous.next = node.next; node.next.previous = node.previous; node.previous = null; node.next = null; size--; return true; } public boolean replace(final String existing, final E value) { final Node node = doFind(existing); if (node == null) { return false; } node.value = value; return true; } public int getSize() { return size; } public class Node { private final String name; private E value; private Node previous; private Node next; Node(final String name, final E value) { this.name = name; this.value = value; } public String getName() { return name; } public E getValue() { return value; } public Node getPrevious() { return previous != master ? previous : null; } public Node getNext() { return next != master ? next : null; } @Override public String toString() { return name + ": " + value; } } }httpcore5/src/main/java/org/apache/hc/core5/http/config/Registry.java0100644 0000000 0000000 00000004047 00000250600 024457 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.config; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.util.TextUtils; /** * Generic registry of items keyed by low-case string ID. * * @param the type of values to lookup. * @since 4.3 */ @Contract(threading = ThreadingBehavior.SAFE) public final class Registry implements Lookup { private final Map map; Registry(final Map map) { super(); this.map = new ConcurrentHashMap<>(map); } @Override public I lookup(final String key) { if (key == null) { return null; } return map.get(TextUtils.toLowerCase(key)); } @Override public String toString() { return map.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/config/RegistryBuilder.java0100644 0000000 0000000 00000005053 00000250600 025764 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.config; import java.util.HashMap; import java.util.Map; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TextUtils; /** * Builder for {@link Registry} instances. * * @param the type of Registry values. * @since 4.3 */ public final class RegistryBuilder { private final Map items; /** * Creates a new RegistryBuilder. * * @param the type of Registry values. * @return a new RegistryBuilder. */ public static RegistryBuilder create() { return new RegistryBuilder<>(); } RegistryBuilder() { super(); this.items = new HashMap<>(); } /** * Registers the given item for the given ID. * * @param id The ID key, converted to lower-case. * @param item The item to register. * @return this instance. */ public RegistryBuilder register(final String id, final I item) { Args.notEmpty(id, "ID"); Args.notNull(item, "Item"); items.put(TextUtils.toLowerCase(id), item); return this; } /** * Creates a new Registry with the registered items. * * @return a new Registry with the registered items. */ public Registry build() { return new Registry<>(items); } @Override public String toString() { return items.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/config/package-info.java0100644 0000000 0000000 00000002356 00000250600 025174 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Core configuration APIs. */ package org.apache.hc.core5.http.config; httpcore5/src/main/java/org/apache/hc/core5/http/impl/0040755 0000000 0000000 00000000000 00000250600 021476 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/impl/BasicEndpointDetails.java0100644 0000000 0000000 00000004550 00000250600 026372 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl; import java.net.SocketAddress; import org.apache.hc.core5.http.EndpointDetails; import org.apache.hc.core5.http.HttpConnectionMetrics; import org.apache.hc.core5.util.Timeout; /** * Basic HTTP connection endpoint details. * * @since 5.0 */ public final class BasicEndpointDetails extends EndpointDetails { private final HttpConnectionMetrics metrics; public BasicEndpointDetails( final SocketAddress remoteAddress, final SocketAddress localAddress, final HttpConnectionMetrics metrics, final Timeout socketTimeout) { super(remoteAddress, localAddress, socketTimeout); this.metrics = metrics; } @Override public long getRequestCount() { return metrics != null ? metrics.getRequestCount() : 0; } @Override public long getResponseCount() { return metrics != null ? metrics.getResponseCount() : 0; } @Override public long getSentBytesCount() { return metrics != null ? metrics.getSentBytesCount() : 0; } @Override public long getReceivedBytesCount() { return metrics != null ? metrics.getReceivedBytesCount() : 0; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/BasicEntityDetails.java0100644 0000000 0000000 00000004230 00000250600 026061 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl; import java.util.Collections; import java.util.Objects; import java.util.Set; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.ContentType; /** * Basic HTTP message entity details. * * @since 5.0 */ public final class BasicEntityDetails implements EntityDetails { private final long len; private final ContentType contentType; public BasicEntityDetails(final long len, final ContentType contentType) { this.len = len; this.contentType = contentType; } @Override public long getContentLength() { return len; } @Override public String getContentType() { return Objects.toString(contentType, null); } @Override public String getContentEncoding() { return null; } @Override public boolean isChunked() { return false; } @Override public Set getTrailerNames() { return Collections.emptySet(); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/BasicHttpConnectionMetrics.java0100644 0000000 0000000 00000005603 00000250600 027572 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl; import java.util.concurrent.atomic.AtomicLong; import org.apache.hc.core5.http.HttpConnectionMetrics; import org.apache.hc.core5.http.io.HttpTransportMetrics; /** * Default implementation of the {@link HttpConnectionMetrics} interface. * * @since 4.0 */ public final class BasicHttpConnectionMetrics implements HttpConnectionMetrics { private final HttpTransportMetrics inTransportMetric; private final HttpTransportMetrics outTransportMetric; private final AtomicLong requestCount; private final AtomicLong responseCount; public BasicHttpConnectionMetrics( final HttpTransportMetrics inTransportMetric, final HttpTransportMetrics outTransportMetric) { super(); this.inTransportMetric = inTransportMetric; this.outTransportMetric = outTransportMetric; this.requestCount = new AtomicLong(0); this.responseCount = new AtomicLong(0); } @Override public long getReceivedBytesCount() { if (this.inTransportMetric != null) { return this.inTransportMetric.getBytesTransferred(); } return -1; } @Override public long getSentBytesCount() { if (this.outTransportMetric != null) { return this.outTransportMetric.getBytesTransferred(); } return -1; } @Override public long getRequestCount() { return this.requestCount.get(); } public void incrementRequestCount() { this.requestCount.incrementAndGet(); } @Override public long getResponseCount() { return this.responseCount.get(); } public void incrementResponseCount() { this.responseCount.incrementAndGet(); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/BasicHttpTransportMetrics.java0100644 0000000 0000000 00000003504 00000250600 027465 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl; import java.util.concurrent.atomic.AtomicLong; import org.apache.hc.core5.http.io.HttpTransportMetrics; /** * Default implementation of {@link HttpTransportMetrics}. * * @since 4.0 */ public class BasicHttpTransportMetrics implements HttpTransportMetrics { private final AtomicLong bytesTransferred; public BasicHttpTransportMetrics() { this.bytesTransferred = new AtomicLong(0); } @Override public long getBytesTransferred() { return this.bytesTransferred.get(); } public void incrementBytesTransferred(final long count) { this.bytesTransferred.addAndGet(count); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/CharCodingSupport.java0100644 0000000 0000000 00000005467 00000250600 025750 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.nio.charset.CodingErrorAction; import org.apache.hc.core5.http.config.CharCodingConfig; public final class CharCodingSupport { private CharCodingSupport() { } public static CharsetDecoder createDecoder(final CharCodingConfig cconfig) { if (cconfig == null) { return null; } final Charset charset = cconfig.getCharset(); final CodingErrorAction malformed = cconfig.getMalformedInputAction(); final CodingErrorAction unmappable = cconfig.getUnmappableInputAction(); if (charset != null) { return charset.newDecoder() .onMalformedInput(malformed != null ? malformed : CodingErrorAction.REPORT) .onUnmappableCharacter(unmappable != null ? unmappable : CodingErrorAction.REPORT); } return null; } public static CharsetEncoder createEncoder(final CharCodingConfig cconfig) { if (cconfig == null) { return null; } final Charset charset = cconfig.getCharset(); if (charset != null) { final CodingErrorAction malformed = cconfig.getMalformedInputAction(); final CodingErrorAction unmappable = cconfig.getUnmappableInputAction(); return charset.newEncoder() .onMalformedInput(malformed != null ? malformed : CodingErrorAction.REPORT) .onUnmappableCharacter(unmappable != null ? unmappable : CodingErrorAction.REPORT); } return null; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/DefaultAddressResolver.java0100644 0000000 0000000 00000004443 00000250600 026757 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl; import java.net.InetSocketAddress; import org.apache.hc.core5.function.Resolver; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.URIScheme; /** * Default {@link HttpHost} to {@link InetSocketAddress} resolver. * * @since 5.0 */ public final class DefaultAddressResolver implements Resolver { /** * The singleton instance. */ public static final DefaultAddressResolver INSTANCE = new DefaultAddressResolver(); @Override public InetSocketAddress resolve(final HttpHost host) { if (host == null) { return null; } int port = host.getPort(); if (port < 0) { final String scheme = host.getSchemeName(); if (URIScheme.HTTP.same(scheme)) { port = 80; } else if (URIScheme.HTTPS.same(scheme)) { port = 443; } } if (host.getAddress() != null) { return new InetSocketAddress(host.getAddress(), port); } return new InetSocketAddress(host.getHostName(), port); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/DefaultConnectionReuseStrategy.java0100644 0000000 0000000 00000016247 00000250600 030503 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl; import java.util.Iterator; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.message.BasicTokenIterator; import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * Default implementation of a strategy deciding about connection re-use. The strategy * determines whether a connection is persistent or not based on the message’s protocol * version and {@code Connection} header field if present. Connections will not be * re-used and will close if any of these conditions is met *

    *
  • the {@code close} connection option is present in the request message
  • *
  • the response message content body is incorrectly or ambiguously delineated
  • *
  • the {@code close} connection option is present in the response message
  • *
  • If the received protocol is {@code HTTP/1.0} (or earlier) and {@code keep-alive} * connection option is not present
  • *
* In the absence of a {@code Connection} header field, the non-standard but commonly used * {@code Proxy-Connection} header field will be used instead. If no connection options are * explicitly given the default policy for the HTTP version is applied. {@code HTTP/1.1} * (or later) connections are re-used by default. {@code HTTP/1.0} (or earlier) connections * are not re-used by default. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class DefaultConnectionReuseStrategy implements ConnectionReuseStrategy { public static final DefaultConnectionReuseStrategy INSTANCE = new DefaultConnectionReuseStrategy(); public DefaultConnectionReuseStrategy() { super(); } // see interface ConnectionReuseStrategy @Override public boolean keepAlive( final HttpRequest request, final HttpResponse response, final HttpContext context) { Args.notNull(response, "HTTP response"); if (request != null) { // Consider framing of a request message with both Content-Length and Content-Length headers faulty if (request.containsHeader(HttpHeaders.CONTENT_LENGTH) && request.containsHeader(HttpHeaders.TRANSFER_ENCODING)) { return false; } final Iterator it = MessageSupport.iterateTokens(request, HttpHeaders.CONNECTION); while (it.hasNext()) { final String token = it.next(); if (HeaderElements.CLOSE.equalsIgnoreCase(token)) { return false; } } } // If Transfer-Encoding is not present consider framing of a response message // with multiple Content-Length headers faulty final Header teh = response.getFirstHeader(HttpHeaders.TRANSFER_ENCODING); if (teh == null && MessageSupport.canResponseHaveBody(request != null ? request.getMethod() : null, response) && response.countHeaders(HttpHeaders.CONTENT_LENGTH) != 1) { return false; } final ProtocolVersion ver = response.getVersion() != null ? response.getVersion() : context.getProtocolVersion(); // Consider framing of a HTTP/1.0 response message with Transfer-Content header faulty if (ver.lessEquals(HttpVersion.HTTP_1_0) && teh != null) { return false; } // If a HTTP 204 No Content response contains a Content-length with value > 0 or Transfer-Encoding header, // don't reuse the connection. This is to avoid getting out-of-sync if a misbehaved HTTP server // returns content as part of a HTTP 204 response. if (response.getCode() == HttpStatus.SC_NO_CONTENT) { final Header clh = response.getFirstHeader(HttpHeaders.CONTENT_LENGTH); if (clh != null) { try { final long contentLen = Long.parseLong(clh.getValue()); if (contentLen > 0) { return false; } } catch (final NumberFormatException ex) { // fall through } } if (response.containsHeader(HttpHeaders.TRANSFER_ENCODING)) { return false; } } // Check for the "Connection" header. If that is absent, check for // the "Proxy-Connection" header. The latter is an unspecified and // broken but unfortunately common extension of HTTP. Iterator
headerIterator = response.headerIterator(HttpHeaders.CONNECTION); if (!headerIterator.hasNext()) { headerIterator = response.headerIterator("Proxy-Connection"); } if (headerIterator.hasNext()) { if (ver.greaterEquals(HttpVersion.HTTP_1_1)) { final Iterator it = new BasicTokenIterator(headerIterator); while (it.hasNext()) { final String token = it.next(); if (HeaderElements.CLOSE.equalsIgnoreCase(token)) { return false; } } return true; } final Iterator it = new BasicTokenIterator(headerIterator); while (it.hasNext()) { final String token = it.next(); if (HeaderElements.KEEP_ALIVE.equalsIgnoreCase(token)) { return true; } } return false; } return ver.greaterEquals(HttpVersion.HTTP_1_1); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/DefaultContentLengthStrategy.java0100644 0000000 0000000 00000011004 00000250600 030136 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpMessage; import org.apache.hc.core5.http.NotImplementedException; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TextUtils; /** * The default implementation of the content length strategy. This class * will throw {@link ProtocolException} if it encounters an unsupported * transfer encoding, multiple {@code Content-Length} header * values or a malformed {@code Content-Length} header value. *

* This class recognizes "chunked" transfer-coding only. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class DefaultContentLengthStrategy implements ContentLengthStrategy { public static final DefaultContentLengthStrategy INSTANCE = new DefaultContentLengthStrategy(); /** * Creates {@code DefaultContentLengthStrategy} instance. {@link ContentLengthStrategy#UNDEFINED} * is used per default when content length is not explicitly specified in the message. */ public DefaultContentLengthStrategy() { } enum Coding { UNKNOWN, CHUNK } @Override public long determineLength(final HttpMessage message) throws HttpException { Args.notNull(message, "HTTP message"); final Header teh = message.getFirstHeader(HttpHeaders.TRANSFER_ENCODING); if (teh != null) { final AtomicReference codingRef = new AtomicReference<>(); MessageSupport.parseTokens(message, HttpHeaders.TRANSFER_ENCODING, e -> { if (!TextUtils.isBlank(e)) { if (e.equalsIgnoreCase(HeaderElements.CHUNKED_ENCODING)) { if (!codingRef.compareAndSet(null, Coding.CHUNK)) { codingRef.set(Coding.UNKNOWN); } } else { codingRef.set(Coding.UNKNOWN); } } }); if (codingRef.get() == Coding.CHUNK) { return CHUNKED; } throw new NotImplementedException("Unsupported transfer encoding: " + teh.getValue()); } if (message.countHeaders(HttpHeaders.CONTENT_LENGTH) > 1) { throw new ProtocolException("Multiple Content-Length headers"); } final Header contentLengthHeader = message.getFirstHeader(HttpHeaders.CONTENT_LENGTH); if (contentLengthHeader != null) { final String s = contentLengthHeader.getValue(); try { final long len = Long.parseLong(s); if (len < 0) { throw new ProtocolException("Negative content length: " + s); } return len; } catch (final NumberFormatException e) { throw new ProtocolException("Invalid content length: " + s); } } return UNDEFINED; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/EnglishReasonPhraseCatalog.java0100644 0000000 0000000 00000023054 00000250600 027541 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl; import java.util.Locale; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.ReasonPhraseCatalog; import org.apache.hc.core5.util.Args; /** * English reason phrases for HTTP status codes. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class EnglishReasonPhraseCatalog implements ReasonPhraseCatalog { // static array with english reason phrases defined below /** * The default instance of this catalog. * This catalog is thread safe, so there typically * is no need to create other instances. */ public final static EnglishReasonPhraseCatalog INSTANCE = new EnglishReasonPhraseCatalog(); /** * Restricted default constructor, for derived classes. * If you need an instance of this class, use {@link #INSTANCE INSTANCE}. */ protected EnglishReasonPhraseCatalog() { // no body } /** * Obtains the reason phrase for a status code. * * @param status the status code, in the range 100-599 * @param loc ignored * * @return the reason phrase, or {@code null} */ @Override public String getReason(final int status, final Locale loc) { Args.checkRange(status, 100, 599, "Unknown category for status code"); final int category = status / 100; final int subcode = status - 100 * category; String reason = null; if (REASON_PHRASES[category].length > subcode) { reason = REASON_PHRASES[category][subcode]; } return reason; } /** Reason phrases lookup table. */ private static final String[][] REASON_PHRASES = new String[][]{ null, new String[4], // 1xx new String[27], // 2xx new String[9], // 3xx new String[52], // 4xx new String[12] // 5xx }; /** * Stores the given reason phrase, by status code. * Helper method to initialize the static lookup table. * * @param status the status code for which to define the phrase * @param reason the reason phrase for this status code */ private static void setReason(final int status, final String reason) { final int category = status / 100; final int subcode = status - 100 * category; REASON_PHRASES[category][subcode] = reason; } // ----------------------------------------------------- Static Initializer /** Set up status code to "reason phrase" map. */ static { // HTTP 1.1 Server status codes -- see RFC 9110 setReason(HttpStatus.SC_OK, "OK"); setReason(HttpStatus.SC_CREATED, "Created"); setReason(HttpStatus.SC_ACCEPTED, "Accepted"); setReason(HttpStatus.SC_NO_CONTENT, "No Content"); setReason(HttpStatus.SC_MOVED_PERMANENTLY, "Moved Permanently"); setReason(HttpStatus.SC_MOVED_TEMPORARILY, "Moved Temporarily"); setReason(HttpStatus.SC_NOT_MODIFIED, "Not Modified"); setReason(HttpStatus.SC_BAD_REQUEST, "Bad Request"); setReason(HttpStatus.SC_UNAUTHORIZED, "Unauthorized"); setReason(HttpStatus.SC_FORBIDDEN, "Forbidden"); setReason(HttpStatus.SC_NOT_FOUND, "Not Found"); setReason(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Internal Server Error"); setReason(HttpStatus.SC_NOT_IMPLEMENTED, "Not Implemented"); setReason(HttpStatus.SC_BAD_GATEWAY, "Bad Gateway"); setReason(HttpStatus.SC_SERVICE_UNAVAILABLE, "Service Unavailable"); setReason(HttpStatus.SC_CONTINUE, "Continue"); setReason(HttpStatus.SC_TEMPORARY_REDIRECT, "Temporary Redirect"); setReason(HttpStatus.SC_METHOD_NOT_ALLOWED, "Method Not Allowed"); setReason(HttpStatus.SC_CONFLICT, "Conflict"); setReason(HttpStatus.SC_PRECONDITION_FAILED, "Precondition Failed"); setReason(HttpStatus.SC_REQUEST_TOO_LONG, "Request Too Long"); setReason(HttpStatus.SC_REQUEST_URI_TOO_LONG, "Request-URI Too Long"); setReason(HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type"); setReason(HttpStatus.SC_MULTIPLE_CHOICES, "Multiple Choices"); setReason(HttpStatus.SC_SEE_OTHER, "See Other"); setReason(HttpStatus.SC_USE_PROXY, "Use Proxy"); setReason(HttpStatus.SC_PAYMENT_REQUIRED, "Payment Required"); setReason(HttpStatus.SC_NOT_ACCEPTABLE, "Not Acceptable"); setReason(HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED, "Proxy Authentication Required"); setReason(HttpStatus.SC_REQUEST_TIMEOUT, "Request Timeout"); setReason(HttpStatus.SC_SWITCHING_PROTOCOLS, "Switching Protocols"); setReason(HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION, "Non Authoritative Information"); setReason(HttpStatus.SC_RESET_CONTENT, "Reset Content"); setReason(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content"); setReason(HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout"); setReason(HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED, "Http Version Not Supported"); setReason(HttpStatus.SC_GONE, "Gone"); setReason(HttpStatus.SC_LENGTH_REQUIRED, "Length Required"); setReason(HttpStatus.SC_REQUESTED_RANGE_NOT_SATISFIABLE, "Requested Range Not Satisfiable"); setReason(HttpStatus.SC_EXPECTATION_FAILED, "Expectation Failed"); setReason(HttpStatus.SC_MISDIRECTED_REQUEST, "Misdirected Request"); // WebDAV Server-specific status codes setReason(HttpStatus.SC_PROCESSING, "Processing"); setReason(HttpStatus.SC_MULTI_STATUS, "Multi-Status"); setReason(HttpStatus.SC_ALREADY_REPORTED, "Already Reported"); setReason(HttpStatus.SC_IM_USED, "IM Used"); setReason(HttpStatus.SC_UNPROCESSABLE_CONTENT, "Unprocessable Content"); setReason(HttpStatus.SC_INSUFFICIENT_SPACE_ON_RESOURCE, "Insufficient Space On Resource"); setReason(HttpStatus.SC_METHOD_FAILURE, "Method Failure"); setReason(HttpStatus.SC_LOCKED, "Locked"); setReason(HttpStatus.SC_INSUFFICIENT_STORAGE, "Insufficient Storage"); setReason(HttpStatus.SC_LOOP_DETECTED, "Loop Detected"); setReason(HttpStatus.SC_NOT_EXTENDED, "Not Extended"); setReason(HttpStatus.SC_FAILED_DEPENDENCY, "Failed Dependency"); setReason(HttpStatus.SC_TOO_EARLY, "Too Early"); setReason(HttpStatus.SC_UPGRADE_REQUIRED, "Upgrade Required"); // Additional HTTP Status Code - see RFC 6585 setReason(HttpStatus.SC_PRECONDITION_REQUIRED, "Precondition Required"); setReason(HttpStatus.SC_TOO_MANY_REQUESTS, "Too Many Requests"); setReason(HttpStatus.SC_REQUEST_HEADER_FIELDS_TOO_LARGE, "Request Header Fields Too Large"); setReason(HttpStatus.SC_NETWORK_AUTHENTICATION_REQUIRED, "Network Authentication Required"); // Early Hints - see RFC 8297 setReason(HttpStatus.SC_EARLY_HINTS, "Early Hints"); //Permanent Redirect - see RFC 7538 setReason(HttpStatus.SC_PERMANENT_REDIRECT, "Permanent Redirect"); // Legal Obstacles - see RFC 7725 setReason(HttpStatus.SC_UNAVAILABLE_FOR_LEGAL_REASONS, "Unavailable For Legal Reasons"); // Transparent Content Negotiation - see RFC 2295 setReason(HttpStatus.SC_VARIANT_ALSO_NEGOTIATES, "Variant Also Negotiates"); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/Http1StreamListener.java0100644 0000000 0000000 00000003561 00000250600 026225 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; /** * HTTP/1.1 stream event listener. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.STATELESS) @Internal public interface Http1StreamListener { void onRequestHead(HttpConnection connection, HttpRequest request); void onResponseHead(HttpConnection connection, HttpResponse response); void onExchangeComplete(HttpConnection connection, boolean keepAlive); } httpcore5/src/main/java/org/apache/hc/core5/http/impl/HttpProcessors.java0100644 0000000 0000000 00000021435 00000250600 025345 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http.protocol.HttpProcessorBuilder; import org.apache.hc.core5.http.protocol.RequestConformance; import org.apache.hc.core5.http.protocol.RequestConnControl; import org.apache.hc.core5.http.protocol.RequestContent; import org.apache.hc.core5.http.protocol.RequestExpectContinue; import org.apache.hc.core5.http.protocol.RequestTE; import org.apache.hc.core5.http.protocol.RequestTargetHost; import org.apache.hc.core5.http.protocol.RequestUserAgent; import org.apache.hc.core5.http.protocol.RequestValidateHost; import org.apache.hc.core5.http.protocol.ResponseConformance; import org.apache.hc.core5.http.protocol.ResponseConnControl; import org.apache.hc.core5.http.protocol.ResponseContent; import org.apache.hc.core5.http.protocol.ResponseDate; import org.apache.hc.core5.http.protocol.ResponseServer; import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.util.VersionInfo; /** * Factory class for standard {@link HttpProcessor} instances. * * @since 5.0 */ public final class HttpProcessors { private final static String SOFTWARE = "Apache-HttpCore"; /** * Creates {@link HttpProcessorBuilder} initialized with default protocol interceptors * for server side HTTP/1.1 processing. * * @param serverInfo the server info text or {@code null} for default. * @return the processor builder. */ public static HttpProcessorBuilder customServer(final String serverInfo) { return HttpProcessorBuilder.create() .addAll( ResponseConformance.INSTANCE, ResponseDate.INSTANCE, new ResponseServer(!TextUtils.isBlank(serverInfo) ? serverInfo : VersionInfo.getSoftwareInfo(SOFTWARE, "org.apache.hc.core5", HttpProcessors.class)), ResponseContent.INSTANCE, ResponseConnControl.INSTANCE) .addAll( RequestValidateHost.INSTANCE, RequestConformance.INSTANCE); } /** * Creates {@link HttpProcessor} initialized with default protocol interceptors * for server side HTTP/1.1 processing. * * @param serverInfo the server info text or {@code null} for default. * @return the processor. */ public static HttpProcessor server(final String serverInfo) { return customServer(serverInfo).build(); } /** * Creates {@link HttpProcessor} initialized with default protocol interceptors * for server side HTTP/1.1 processing. * * @return the processor. */ public static HttpProcessor server() { return customServer(null).build(); } /** * Creates {@link HttpProcessorBuilder} initialized with default protocol interceptors * for client side HTTP/1.1 processing. * * @param agentInfo the agent info text or {@code null} for default. * @return the processor builder. */ public static HttpProcessorBuilder customClient(final String agentInfo) { return HttpProcessorBuilder.create() .addAll( RequestTargetHost.INSTANCE, RequestContent.INSTANCE, RequestConnControl.INSTANCE, new RequestUserAgent(!TextUtils.isBlank(agentInfo) ? agentInfo : VersionInfo.getSoftwareInfo(SOFTWARE, "org.apache.hc.core5", HttpProcessors.class)), RequestExpectContinue.INSTANCE); } /** * Creates an {@link HttpProcessorBuilder} initialized with strict protocol interceptors * for client-side HTTP/1.1 processing. *

* This configuration enforces stricter validation and processing of client requests, * ensuring compliance with the HTTP protocol. It includes interceptors for handling * target hosts, content, connection controls, and TE header validation, among others. * The user agent can be customized using the provided {@code agentInfo} parameter. * * @param agentInfo the user agent info to be included in the {@code User-Agent} header. * If {@code null} or blank, a default value will be used. * @return the {@link HttpProcessorBuilder} configured with strict client-side interceptors. * @since 5.4 */ public static HttpProcessorBuilder strictClient(final String agentInfo) { return HttpProcessorBuilder.create() .addAll( RequestTargetHost.INSTANCE, RequestContent.INSTANCE, RequestConnControl.INSTANCE, RequestTE.INSTANCE, new RequestUserAgent(!TextUtils.isBlank(agentInfo) ? agentInfo : VersionInfo.getSoftwareInfo(SOFTWARE, "org.apache.hc.core5", HttpProcessors.class)), RequestExpectContinue.INSTANCE); } /** * Creates {@link HttpProcessorBuilder} initialized with default protocol interceptors * for client side HTTP/1.1 processing. * * @param agentInfo the agent info text or {@code null} for default. * @return the processor builder. * @since 5.4 */ public static HttpProcessorBuilder customClient(final String agentInfo, final boolean strict) { return strict ? strictClient(agentInfo) : customClient(agentInfo); } /** * Creates {@link HttpProcessor} initialized with default protocol interceptors * for client side HTTP/1.1 processing. * * @param agentInfo the agent info text or {@code null} for default. * @return the processor. */ public static HttpProcessor client(final String agentInfo) { return client(agentInfo, false); } /** * Creates {@link HttpProcessor} initialized with default protocol interceptors * for client side HTTP/1.1 processing. * * @return the processor. */ public static HttpProcessor client() { return client(null); } /** * Creates an {@link HttpProcessor} for client-side HTTP/2 processing. * This method allows the option to include strict protocol interceptors. * * @param agentInfo the agent info text or {@code null} for default. * @param strict if {@code true}, strict protocol interceptors will be added, including the {@code TE} header validation. * @return the configured HTTP processor. * @since 5.4 */ public static HttpProcessor client(final String agentInfo, final boolean strict) { return customClient(agentInfo, strict).build(); } /** * Creates an {@link HttpProcessor} for client-side HTTP/2 processing * with strict protocol validation interceptors by default. *

* Strict validation includes additional checks such as validating the {@code TE} header. * * @return the configured strict HTTP processor. * @since 5.4 */ public static HttpProcessor clientStrict() { return customClient(null, true).build(); } /** * Creates an {@link HttpProcessor} for client-side HTTP/2 processing * with strict protocol validation interceptors, using the specified agent information. *

* Strict validation includes additional checks such as validating the {@code TE} header. * * @param agentInfo the agent info text or {@code null} for default. * @return the configured strict HTTP processor. * @since 5.4 */ public static HttpProcessor clientStrict(final String agentInfo) { return customClient(agentInfo, true).build(); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/IncomingEntityDetails.java0100644 0000000 0000000 00000005544 00000250600 026614 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl; import java.util.Collections; import java.util.Set; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.MessageHeaders; import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.util.Args; /** * HTTP message entity details. * * @since 5.0 */ @Internal public class IncomingEntityDetails implements EntityDetails { private final MessageHeaders message; private final long contentLength; public IncomingEntityDetails(final MessageHeaders message, final long contentLength) { this.message = Args.notNull(message, "Message"); this.contentLength = contentLength; } public IncomingEntityDetails(final MessageHeaders message) { this(message, -1); } @Override public long getContentLength() { return contentLength; } @Override public String getContentType() { final Header h = message.getFirstHeader(HttpHeaders.CONTENT_TYPE); return h != null ? h.getValue() : null; } @Override public String getContentEncoding() { final Header h = message.getFirstHeader(HttpHeaders.CONTENT_ENCODING); return h != null ? h.getValue() : null; } @Override public boolean isChunked() { return contentLength < 0; } @Override public Set getTrailerNames() { final Header h = message.getFirstHeader(HttpHeaders.TRAILER); if (h == null) { return Collections.emptySet(); } return MessageSupport.parseTokens(h); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/NoConnectionReuseStrategy.java0100644 0000000 0000000 00000003677 00000250600 027476 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.protocol.HttpContext; /** * A strategy that never reuses a connection. * @since 5.4 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class NoConnectionReuseStrategy implements ConnectionReuseStrategy { public static final NoConnectionReuseStrategy INSTANCE = new NoConnectionReuseStrategy(); @Override public boolean keepAlive(final HttpRequest request, final HttpResponse response, final HttpContext context) { return false; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/ServerSupport.java0100644 0000000 0000000 00000005444 00000250600 025210 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.MethodNotSupportedException; import org.apache.hc.core5.http.MisdirectedRequestException; import org.apache.hc.core5.http.NotImplementedException; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.RequestHeaderFieldsTooLargeException; import org.apache.hc.core5.http.UnsupportedHttpVersionException; /** * HTTP Server support methods. * * @since 5.0 */ @Internal public class ServerSupport { public static String toErrorMessage(final Exception ex) { final String message = ex.getMessage(); return message != null ? message : ex.toString(); } public static int toStatusCode(final Exception ex) { final int code; if (ex instanceof MethodNotSupportedException) { code = HttpStatus.SC_NOT_IMPLEMENTED; } else if (ex instanceof UnsupportedHttpVersionException) { code = HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED; } else if (ex instanceof NotImplementedException) { code = HttpStatus.SC_NOT_IMPLEMENTED; } else if (ex instanceof RequestHeaderFieldsTooLargeException) { code = HttpStatus.SC_REQUEST_HEADER_FIELDS_TOO_LARGE; } else if (ex instanceof MisdirectedRequestException) { code = HttpStatus.SC_MISDIRECTED_REQUEST; } else if (ex instanceof ProtocolException) { code = HttpStatus.SC_BAD_REQUEST; } else { code = HttpStatus.SC_INTERNAL_SERVER_ERROR; } return code; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/package-info.java0100644 0000000 0000000 00000002376 00000250600 024672 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Default implementations of core HTTP APIs. */ package org.apache.hc.core5.http.impl; httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/0040755 0000000 0000000 00000000000 00000250600 023513 5ustar000000000 0000000 ././@LongLink0100644 0000000 0000000 00000000145 00000250600 011611 Lustar 0000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AbstractConnectionInitiatorBase.javahttpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AbstractConnectionInitiatorBase.java0100644 0000000 0000000 00000004120 00000250600 032611 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.bootstrap; import java.net.SocketAddress; import java.util.concurrent.Future; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.reactor.ConnectionInitiator; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.util.Timeout; abstract class AbstractConnectionInitiatorBase implements ConnectionInitiator { @Override public final Future connect( final NamedEndpoint remoteEndpoint, final SocketAddress remoteAddress, final SocketAddress localAddress, final Timeout timeout, final Object attachment, final FutureCallback callback) { return getIOReactor().connect(remoteEndpoint, remoteAddress, localAddress, timeout, attachment, callback); } abstract ConnectionInitiator getIOReactor(); } httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequester.java0100644 0000000 0000000 00000012576 00000250600 027343 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.bootstrap; import java.io.IOException; import java.net.InetSocketAddress; import java.util.concurrent.Future; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.concurrent.DefaultThreadFactory; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.function.Resolver; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.impl.DefaultAddressResolver; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.reactor.ConnectionInitiator; import org.apache.hc.core5.reactor.DefaultConnectingIOReactor; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.IOReactorMetricsListener; import org.apache.hc.core5.reactor.IOReactorService; import org.apache.hc.core5.reactor.IOReactorStatus; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.IOSessionListener; import org.apache.hc.core5.reactor.IOWorkerSelector; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; /** * Protocol agnostic client side I/O session initiator. * * @since 5.0 */ public class AsyncRequester extends AbstractConnectionInitiatorBase implements IOReactorService { private final DefaultConnectingIOReactor ioReactor; private final Resolver addressResolver; @Internal public AsyncRequester( final IOEventHandlerFactory eventHandlerFactory, final IOReactorConfig ioReactorConfig, final Decorator ioSessionDecorator, final Callback exceptionCallback, final IOSessionListener sessionListener, final Callback sessionShutdownCallback, final Resolver addressResolver, final IOReactorMetricsListener threadPoolListener, final IOWorkerSelector workerSelector) { this.ioReactor = new DefaultConnectingIOReactor( eventHandlerFactory, ioReactorConfig, new DefaultThreadFactory("requester-dispatch", true), ioSessionDecorator, exceptionCallback, sessionListener, threadPoolListener, sessionShutdownCallback, workerSelector); this.addressResolver = addressResolver != null ? addressResolver : DefaultAddressResolver.INSTANCE; } @Override ConnectionInitiator getIOReactor() { return ioReactor; } public Future requestSession( final HttpHost host, final Timeout timeout, final Object attachment, final FutureCallback callback) { Args.notNull(host, "Host"); Args.notNull(timeout, "Timeout"); return connect(host, addressResolver.resolve(host), null, timeout, attachment, callback); } @Override public void start() { ioReactor.start(); } @Override public IOReactorStatus getStatus() { return ioReactor.getStatus(); } @Override public void initiateShutdown() { ioReactor.initiateShutdown(); } @Override public void awaitShutdown(final TimeValue waitTime) throws InterruptedException { ioReactor.awaitShutdown(waitTime); } @Override public void close(final CloseMode closeMode) { ioReactor.close(closeMode); } @Override public void close() throws IOException { ioReactor.close(); } @Internal protected static HttpHost defaultTarget(final HttpRequest request) throws ProtocolException { final String scheme = request.getScheme(); final URIAuthority authority = request.getAuthority(); if (authority == null) { throw new ProtocolException("Request authority not specified"); } return new HttpHost(scheme, authority); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequesterBootstrap.java0100644 0000000 0000000 00000025271 00000250600 031235 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.bootstrap; import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.HttpProcessors; import org.apache.hc.core5.http.impl.nio.ClientHttp1IOEventHandlerFactory; import org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexerFactory; import org.apache.hc.core5.http.nio.ssl.BasicClientTlsStrategy; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.pool.ConnPoolListener; import org.apache.hc.core5.pool.DefaultDisposalCallback; import org.apache.hc.core5.pool.LaxConnPool; import org.apache.hc.core5.pool.ManagedConnPool; import org.apache.hc.core5.pool.PoolConcurrencyPolicy; import org.apache.hc.core5.pool.PoolReusePolicy; import org.apache.hc.core5.pool.RouteSegmentedConnPool; import org.apache.hc.core5.pool.StrictConnPool; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.IOReactorMetricsListener; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.IOSessionListener; import org.apache.hc.core5.util.Timeout; /** * {@link HttpAsyncRequester} bootstrap. * * @since 5.0 */ public class AsyncRequesterBootstrap { private IOReactorConfig ioReactorConfig; private Http1Config http1Config; private CharCodingConfig charCodingConfig; private HttpProcessor httpProcessor; private ConnectionReuseStrategy connStrategy; private int defaultMaxPerRoute; private int maxTotal; private Timeout timeToLive; private PoolReusePolicy poolReusePolicy; private PoolConcurrencyPolicy poolConcurrencyPolicy; private TlsStrategy tlsStrategy; private Timeout handshakeTimeout; private Decorator ioSessionDecorator; private Callback exceptionCallback; private IOSessionListener sessionListener; private Http1StreamListener streamListener; private ConnPoolListener connPoolListener; private IOReactorMetricsListener threadPoolListener; private AsyncRequesterBootstrap() { } public static AsyncRequesterBootstrap bootstrap() { return new AsyncRequesterBootstrap(); } /** * Sets I/O reactor configuration. * * @return this instance. */ public final AsyncRequesterBootstrap setIOReactorConfig(final IOReactorConfig ioReactorConfig) { this.ioReactorConfig = ioReactorConfig; return this; } /** * Sets HTTP/1.1 protocol parameters * * @return this instance. */ public final AsyncRequesterBootstrap setHttp1Config(final Http1Config http1Config) { this.http1Config = http1Config; return this; } /** * Sets message char coding. * * @return this instance. */ public final AsyncRequesterBootstrap setCharCodingConfig(final CharCodingConfig charCodingConfig) { this.charCodingConfig = charCodingConfig; return this; } /** * Sets {@link HttpProcessor} instance. * * @return this instance. */ public final AsyncRequesterBootstrap setHttpProcessor(final HttpProcessor httpProcessor) { this.httpProcessor = httpProcessor; return this; } /** * Sets {@link ConnectionReuseStrategy} instance. * * @return this instance. */ public final AsyncRequesterBootstrap setConnectionReuseStrategy(final ConnectionReuseStrategy connStrategy) { this.connStrategy = connStrategy; return this; } public final AsyncRequesterBootstrap setDefaultMaxPerRoute(final int defaultMaxPerRoute) { this.defaultMaxPerRoute = defaultMaxPerRoute; return this; } public final AsyncRequesterBootstrap setMaxTotal(final int maxTotal) { this.maxTotal = maxTotal; return this; } public final AsyncRequesterBootstrap setTimeToLive(final Timeout timeToLive) { this.timeToLive = timeToLive; return this; } /** * Sets {@link PoolReusePolicy} instance. * * @return this instance. */ public final AsyncRequesterBootstrap setPoolReusePolicy(final PoolReusePolicy poolReusePolicy) { this.poolReusePolicy = poolReusePolicy; return this; } /** * Sets {@link PoolConcurrencyPolicy} instance. * * @return this instance. */ @Experimental public final AsyncRequesterBootstrap setPoolConcurrencyPolicy(final PoolConcurrencyPolicy poolConcurrencyPolicy) { this.poolConcurrencyPolicy = poolConcurrencyPolicy; return this; } /** * Sets {@link TlsStrategy} instance. * * @return this instance. */ public final AsyncRequesterBootstrap setTlsStrategy(final TlsStrategy tlsStrategy) { this.tlsStrategy = tlsStrategy; return this; } public final AsyncRequesterBootstrap setTlsHandshakeTimeout(final Timeout handshakeTimeout) { this.handshakeTimeout = handshakeTimeout; return this; } /** * Sets {@link IOSession} {@link Decorator} instance. * * @return this instance. */ public final AsyncRequesterBootstrap setIOSessionDecorator(final Decorator ioSessionDecorator) { this.ioSessionDecorator = ioSessionDecorator; return this; } /** * Sets {@link Exception} {@link Callback} instance. * * @return this instance. */ public final AsyncRequesterBootstrap setExceptionCallback(final Callback exceptionCallback) { this.exceptionCallback = exceptionCallback; return this; } /** * Sets {@link IOSessionListener} instance. * * @return this instance. */ public final AsyncRequesterBootstrap setIOSessionListener(final IOSessionListener sessionListener) { this.sessionListener = sessionListener; return this; } /** * Sets {@link IOReactorMetricsListener} instance. * * @return this instance. * @since 5.4 */ public final AsyncRequesterBootstrap setIOReactorMetricsListener(final IOReactorMetricsListener threadPoolListener) { this.threadPoolListener = threadPoolListener; return this; } /** * Sets {@link Http1StreamListener} instance. * * @return this instance. */ public final AsyncRequesterBootstrap setStreamListener(final Http1StreamListener streamListener) { this.streamListener = streamListener; return this; } /** * Sets {@link ConnPoolListener} instance. * * @return this instance. */ public final AsyncRequesterBootstrap setConnPoolListener(final ConnPoolListener connPoolListener) { this.connPoolListener = connPoolListener; return this; } public HttpAsyncRequester create() { final ManagedConnPool connPool; switch (poolConcurrencyPolicy != null ? poolConcurrencyPolicy : PoolConcurrencyPolicy.STRICT) { case LAX: connPool = new LaxConnPool<>( defaultMaxPerRoute > 0 ? defaultMaxPerRoute : 20, timeToLive, poolReusePolicy, new DefaultDisposalCallback<>(), connPoolListener); break; case OFFLOCK: connPool = new RouteSegmentedConnPool<>( defaultMaxPerRoute > 0 ? defaultMaxPerRoute : 20, maxTotal > 0 ? maxTotal : 50, timeToLive, poolReusePolicy, new DefaultDisposalCallback<>()); break; case STRICT: default: connPool = new StrictConnPool<>( defaultMaxPerRoute > 0 ? defaultMaxPerRoute : 20, maxTotal > 0 ? maxTotal : 50, timeToLive, poolReusePolicy, new DefaultDisposalCallback<>(), connPoolListener); break; } final ClientHttp1StreamDuplexerFactory streamDuplexerFactory = new ClientHttp1StreamDuplexerFactory( httpProcessor != null ? httpProcessor : HttpProcessors.client(), http1Config != null ? http1Config : Http1Config.DEFAULT, charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT, connStrategy, null, null, streamListener); final TlsStrategy tlsStrategyCopy = tlsStrategy != null ? tlsStrategy : new BasicClientTlsStrategy(); final IOEventHandlerFactory ioEventHandlerFactory = new ClientHttp1IOEventHandlerFactory( streamDuplexerFactory, tlsStrategyCopy, handshakeTimeout); return new HttpAsyncRequester( ioReactorConfig, ioEventHandlerFactory, ioSessionDecorator, exceptionCallback, sessionListener, connPool, tlsStrategyCopy, handshakeTimeout, threadPoolListener, null); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServer.java0100644 0000000 0000000 00000012065 00000250600 026623 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.bootstrap; import java.io.IOException; import java.net.SocketAddress; import java.util.Set; import java.util.concurrent.Future; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.concurrent.DefaultThreadFactory; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.ConnectionAcceptor; import org.apache.hc.core5.reactor.ConnectionInitiator; import org.apache.hc.core5.reactor.DefaultListeningIOReactor; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.IOReactorMetricsListener; import org.apache.hc.core5.reactor.IOReactorService; import org.apache.hc.core5.reactor.IOReactorStatus; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.IOSessionListener; import org.apache.hc.core5.reactor.IOWorkerSelector; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.util.TimeValue; /** * Protocol agnostic server side I/O session handler. */ public class AsyncServer extends AbstractConnectionInitiatorBase implements IOReactorService, ConnectionAcceptor { private final DefaultListeningIOReactor ioReactor; @Internal public AsyncServer( final IOEventHandlerFactory eventHandlerFactory, final IOReactorConfig ioReactorConfig, final Decorator ioSessionDecorator, final Callback exceptionCallback, final IOSessionListener sessionListener, final IOReactorMetricsListener threadPoolListener, final Callback sessionShutdownCallback, final IOWorkerSelector workerSelector) { this.ioReactor = new DefaultListeningIOReactor( eventHandlerFactory, ioReactorConfig, new DefaultThreadFactory("server-dispatch", true), new DefaultThreadFactory("server-listener", true), ioSessionDecorator, exceptionCallback, sessionListener, threadPoolListener, sessionShutdownCallback, workerSelector); } @Override public void start() { ioReactor.start(); } @Override ConnectionInitiator getIOReactor() { return ioReactor; } /** * @since 5.1 */ @Override public Future listen( final SocketAddress address, final Object attachment, final FutureCallback callback) { return ioReactor.listen(address, attachment, callback); } @Override public Future listen(final SocketAddress address, final FutureCallback callback) { return listen(address, null, callback); } public Future listen(final SocketAddress address) { return ioReactor.listen(address, null); } @Override public void pause() throws IOException { ioReactor.pause(); } @Override public void resume() throws IOException { ioReactor.resume(); } @Override public Set getEndpoints() { return ioReactor.getEndpoints(); } @Override public IOReactorStatus getStatus() { return ioReactor.getStatus(); } @Override public void initiateShutdown() { ioReactor.initiateShutdown(); } @Override public void awaitShutdown(final TimeValue waitTime) throws InterruptedException { ioReactor.awaitShutdown(waitTime); } @Override public void close(final CloseMode closeMode) { ioReactor.close(closeMode); } @Override public void close() throws IOException { ioReactor.close(); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServerBootstrap.java0100644 0000000 0000000 00000053633 00000250600 030527 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.bootstrap; import java.util.ArrayList; import java.util.List; import java.util.function.BiFunction; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.HttpRequestMapper; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.config.NamedElementChain; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.HttpProcessors; import org.apache.hc.core5.http.impl.nio.DefaultHttpRequestParserFactory; import org.apache.hc.core5.http.impl.nio.DefaultHttpResponseWriterFactory; import org.apache.hc.core5.http.impl.nio.ServerHttp1IOEventHandlerFactory; import org.apache.hc.core5.http.impl.nio.ServerHttp1StreamDuplexerFactory; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncFilterHandler; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.AsyncServerRequestHandler; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.http.nio.support.AsyncServerExpectationFilter; import org.apache.hc.core5.http.nio.support.AsyncServerFilterChainElement; import org.apache.hc.core5.http.nio.support.AsyncServerFilterChainExchangeHandlerFactory; import org.apache.hc.core5.http.nio.support.BasicAsyncServerExpectationDecorator; import org.apache.hc.core5.http.nio.support.BasicServerExchangeHandler; import org.apache.hc.core5.http.nio.support.DefaultAsyncResponseExchangeHandlerFactory; import org.apache.hc.core5.http.nio.support.TerminalAsyncServerFilter; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http.protocol.UriPatternType; import org.apache.hc.core5.net.InetAddressUtils; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.IOReactorMetricsListener; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.IOSessionListener; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; /** * {@link HttpAsyncServer} bootstrap. * * @since 5.0 */ @SuppressWarnings("deprecation") public class AsyncServerBootstrap { private final List>> routeEntries; private final List> filters; private String canonicalHostName; private HttpRequestMapper> requestRouter; private org.apache.hc.core5.http.protocol.LookupRegistry> lookupRegistry; private IOReactorConfig ioReactorConfig; private Http1Config http1Config; private CharCodingConfig charCodingConfig; private HttpProcessor httpProcessor; private ConnectionReuseStrategy connStrategy; private TlsStrategy tlsStrategy; private Timeout handshakeTimeout; private Decorator ioSessionDecorator; private Callback exceptionCallback; private IOSessionListener sessionListener; private Http1StreamListener streamListener; private IOReactorMetricsListener threadPoolListener; private BiFunction authorityResolver; private AsyncServerBootstrap() { this.routeEntries = new ArrayList<>(); this.filters = new ArrayList<>(); } /** * Creates a new AsyncServerBootstrap instance. * * @return a new AsyncServerBootstrap instance. */ public static AsyncServerBootstrap bootstrap() { return new AsyncServerBootstrap(); } /** * Sets canonical name (fully qualified domain name) of the server. * * @param canonicalHostName canonical name (fully qualified domain name) of the server. * @return this instance. */ public final AsyncServerBootstrap setCanonicalHostName(final String canonicalHostName) { this.canonicalHostName = canonicalHostName; return this; } /** * Sets I/O reactor configuration. * * @param ioReactorConfig I/O reactor configuration. * @return this instance. */ public final AsyncServerBootstrap setIOReactorConfig(final IOReactorConfig ioReactorConfig) { this.ioReactorConfig = ioReactorConfig; return this; } /** * Sets HTTP/1.1 protocol parameters. * * @param http1Config HTTP/1.1 protocol parameters. * @return this instance. */ public final AsyncServerBootstrap setHttp1Config(final Http1Config http1Config) { this.http1Config = http1Config; return this; } /** * Sets char coding configuration. * * @param charCodingConfig char coding configuration. * @return this instance. */ public final AsyncServerBootstrap setCharCodingConfig(final CharCodingConfig charCodingConfig) { this.charCodingConfig = charCodingConfig; return this; } /** * Sets {@link HttpProcessor} instance. * * @param httpProcessor {@link HttpProcessor} instance. * @return this instance. */ public final AsyncServerBootstrap setHttpProcessor(final HttpProcessor httpProcessor) { this.httpProcessor = httpProcessor; return this; } /** * Sets {@link ConnectionReuseStrategy} instance. * * @param connStrategy {@link ConnectionReuseStrategy} instance. * @return this instance. */ public final AsyncServerBootstrap setConnectionReuseStrategy(final ConnectionReuseStrategy connStrategy) { this.connStrategy = connStrategy; return this; } /** * Sets {@link TlsStrategy} instance. * * @param tlsStrategy {@link TlsStrategy} instance. * @return this instance. */ public final AsyncServerBootstrap setTlsStrategy(final TlsStrategy tlsStrategy) { this.tlsStrategy = tlsStrategy; return this; } /** * Sets TLS handshake {@link Timeout}. * * @param handshakeTimeout TLS handshake {@link Timeout}. * @return this instance. */ public final AsyncServerBootstrap setTlsHandshakeTimeout(final Timeout handshakeTimeout) { this.handshakeTimeout = handshakeTimeout; return this; } /** * Sets {@link IOSession} {@link Decorator} instance. * * @param ioSessionDecorator {@link IOSession} {@link Decorator} instance. * @return this instance. */ public final AsyncServerBootstrap setIOSessionDecorator(final Decorator ioSessionDecorator) { this.ioSessionDecorator = ioSessionDecorator; return this; } /** * Sets {@link IOReactorMetricsListener} instance. * * @return this instance. * @since 5.4 */ public final AsyncServerBootstrap setIOReactorMetricsListener(final IOReactorMetricsListener threadPoolListener) { this.threadPoolListener = threadPoolListener; return this; } /** * Sets {@link Exception} {@link Callback} instance. * * @param exceptionCallback {@link Exception} {@link Callback} instance. * @return this instance. */ public final AsyncServerBootstrap setExceptionCallback(final Callback exceptionCallback) { this.exceptionCallback = exceptionCallback; return this; } /** * Sets {@link IOSessionListener} instance. * * @param sessionListener {@link IOSessionListener} instance. * @return this instance. */ public final AsyncServerBootstrap setIOSessionListener(final IOSessionListener sessionListener) { this.sessionListener = sessionListener; return this; } /** * Sets a LookupRegistry for Suppliers of AsyncServerExchangeHandler. * * @param lookupRegistry LookupRegistry for Suppliers of AsyncServerExchangeHandler. * @return this instance. * @deprecated Use {@link RequestRouter}. */ @Deprecated public final AsyncServerBootstrap setLookupRegistry(final org.apache.hc.core5.http.protocol.LookupRegistry> lookupRegistry) { this.lookupRegistry = lookupRegistry; return this; } /** * Sets {@link HttpRequestMapper} instance. * * @param requestRouter {@link HttpRequestMapper} instance. * @return this instance. * @see org.apache.hc.core5.http.impl.routing.RequestRouter * @since 5.3 */ public final AsyncServerBootstrap setRequestRouter(final HttpRequestMapper> requestRouter) { this.requestRouter = requestRouter; return this; } /** * Sets {@link Http1StreamListener} instance. * * @param streamListener {@link Http1StreamListener} instance. * @return this instance. * @since 5.0 */ public final AsyncServerBootstrap setStreamListener(final Http1StreamListener streamListener) { this.streamListener = streamListener; return this; } /** * Sets authority resolver to be used when creating the {@link RequestRouter}. * * @return this instance. * @since 5.4 */ public final AsyncServerBootstrap setAuthorityResolver(final BiFunction authorityResolver) { this.authorityResolver = authorityResolver; return this; } /** * Registers the given {@link AsyncServerExchangeHandler} {@link Supplier} as a default handler for URIs * matching the given pattern. * * @param uriPattern the non-null pattern to register the handler for. * @param supplier the non-null handler supplier. * @return this instance. */ public final AsyncServerBootstrap register(final String uriPattern, final Supplier supplier) { Args.notBlank(uriPattern, "URI pattern"); Args.notNull(supplier, "Exchange handler supplier"); routeEntries.add(new RequestRouter.Entry<>(uriPattern, supplier)); return this; } /** * Registers the given {@link AsyncServerExchangeHandler} {@link Supplier} as a handler for URIs * matching the given host and pattern. * * @param hostname the non-null host name. * @param uriPattern the non-null pattern to register the handler for. * @param supplier the non-null handler supplier. * @return this instance. * @since 5.3 */ public final AsyncServerBootstrap register(final String hostname, final String uriPattern, final Supplier supplier) { Args.notBlank(hostname, "Hostname"); Args.notBlank(uriPattern, "URI pattern"); Args.notNull(supplier, "Exchange handler supplier"); routeEntries.add(new RequestRouter.Entry<>(hostname, uriPattern, supplier)); return this; } /** * Registers the given {@link AsyncServerExchangeHandler} {@link Supplier} as a handler for URIs * matching the given host and pattern. * * @param hostname the host name. * @param uriPattern the pattern to register the handler for. * @param supplier the handler supplier. * @return this instance. * @deprecated use {@link #register(String, String, Supplier)}. */ @Deprecated public final AsyncServerBootstrap registerVirtual(final String hostname, final String uriPattern, final Supplier supplier) { return register(hostname, uriPattern, supplier); } /** * Registers the given {@link AsyncServerRequestHandler} as a default handler for URIs * matching the given pattern. * * @param the AsyncServerRequestHandler request representation type. * @param uriPattern the pattern to register the handler for. * @param requestHandler the handler. * @return this instance. */ public final AsyncServerBootstrap register( final String uriPattern, final AsyncServerRequestHandler requestHandler) { register(uriPattern, () -> new BasicServerExchangeHandler<>(requestHandler)); return this; } /** * Registers the given {@link AsyncServerRequestHandler} as a handler for URIs * matching the given host and pattern. * * @param the request type. * @param hostname the host name * @param uriPattern the pattern to register the handler for. * @param requestHandler the handler. * @return this instance. * @since 5.3 */ public final AsyncServerBootstrap register(final String hostname, final String uriPattern, final AsyncServerRequestHandler requestHandler) { register(hostname, uriPattern, () -> new BasicServerExchangeHandler<>(requestHandler)); return this; } /** * @param the request type. * @return this instance. * @deprecated Use {@link #register(String, String, AsyncServerRequestHandler)}. */ @Deprecated public final AsyncServerBootstrap registerVirtual( final String hostname, final String uriPattern, final AsyncServerRequestHandler requestHandler) { return register(hostname, uriPattern, requestHandler); } /** * Adds the filter before the filter with the given name. * * @return this instance. */ public final AsyncServerBootstrap addFilterBefore(final String existing, final String name, final AsyncFilterHandler filterHandler) { Args.notBlank(existing, "Existing"); Args.notBlank(name, "Name"); Args.notNull(filterHandler, "Filter handler"); filters.add(new FilterEntry<>(FilterEntry.Position.BEFORE, name, filterHandler, existing)); return this; } /** * Adds the filter after the filter with the given name. * * @return this instance. */ public final AsyncServerBootstrap addFilterAfter(final String existing, final String name, final AsyncFilterHandler filterHandler) { Args.notBlank(existing, "Existing"); Args.notBlank(name, "Name"); Args.notNull(filterHandler, "Filter handler"); filters.add(new FilterEntry<>(FilterEntry.Position.AFTER, name, filterHandler, existing)); return this; } /** * Replaces an existing filter with the given name with new filter. * * @return this instance. */ public final AsyncServerBootstrap replaceFilter(final String existing, final AsyncFilterHandler filterHandler) { Args.notBlank(existing, "Existing"); Args.notNull(filterHandler, "Filter handler"); filters.add(new FilterEntry<>(FilterEntry.Position.REPLACE, existing, filterHandler, existing)); return this; } /** * Adds an filter to the head of the processing list. * * @return this instance. */ public final AsyncServerBootstrap addFilterFirst(final String name, final AsyncFilterHandler filterHandler) { Args.notNull(name, "Name"); Args.notNull(filterHandler, "Filter handler"); filters.add(new FilterEntry<>(FilterEntry.Position.FIRST, name, filterHandler, null)); return this; } /** * Adds an filter to the tail of the processing list. * * @return this instance. */ public final AsyncServerBootstrap addFilterLast(final String name, final AsyncFilterHandler filterHandler) { Args.notNull(name, "Name"); Args.notNull(filterHandler, "Filter handler"); filters.add(new FilterEntry<>(FilterEntry.Position.LAST, name, filterHandler, null)); return this; } public HttpAsyncServer create() { final String actualCanonicalHostName = canonicalHostName != null ? canonicalHostName : InetAddressUtils.getCanonicalLocalHostName(); final HttpRequestMapper> requestRouterCopy; if (lookupRegistry != null && requestRouter == null) { final org.apache.hc.core5.http.protocol.RequestHandlerRegistry> handlerRegistry = new org.apache.hc.core5.http.protocol.RequestHandlerRegistry<>( actualCanonicalHostName, () -> lookupRegistry != null ? lookupRegistry : new org.apache.hc.core5.http.protocol.UriPatternMatcher<>()); for (final RequestRouter.Entry> entry: routeEntries) { handlerRegistry.register(entry.uriAuthority != null ? entry.uriAuthority.getHostName() : null, entry.route.pattern, entry.route.handler); } requestRouterCopy = handlerRegistry; } else { if (routeEntries.isEmpty()) { requestRouterCopy = requestRouter; } else { requestRouterCopy = RequestRouter.create( new URIAuthority(actualCanonicalHostName), UriPatternType.URI_PATTERN, routeEntries, authorityResolver != null ? authorityResolver : RequestRouter.IGNORE_PORT_AUTHORITY_RESOLVER, requestRouter); } } final HandlerFactory handlerFactory; if (!filters.isEmpty()) { final NamedElementChain filterChainDefinition = new NamedElementChain<>(); filterChainDefinition.addLast( new TerminalAsyncServerFilter(new DefaultAsyncResponseExchangeHandlerFactory(requestRouterCopy)), StandardFilter.MAIN_HANDLER.name()); filterChainDefinition.addFirst( new AsyncServerExpectationFilter(), StandardFilter.EXPECT_CONTINUE.name()); for (final FilterEntry entry: filters) { switch (entry.position) { case AFTER: filterChainDefinition.addAfter(entry.existing, entry.filterHandler, entry.name); break; case BEFORE: filterChainDefinition.addBefore(entry.existing, entry.filterHandler, entry.name); break; case REPLACE: filterChainDefinition.replace(entry.existing, entry.filterHandler); break; case FIRST: filterChainDefinition.addFirst(entry.filterHandler, entry.name); break; case LAST: // Don't add last, after TerminalAsyncServerFilter, as that does not delegate to the chain // Instead, add the filter just before it, making it effectively the last filter filterChainDefinition.addBefore(StandardFilter.MAIN_HANDLER.name(), entry.filterHandler, entry.name); break; } } NamedElementChain.Node current = filterChainDefinition.getLast(); AsyncServerFilterChainElement execChain = null; while (current != null) { execChain = new AsyncServerFilterChainElement(current.getValue(), execChain); current = current.getPrevious(); } handlerFactory = new AsyncServerFilterChainExchangeHandlerFactory(execChain, exceptionCallback); } else { handlerFactory = new DefaultAsyncResponseExchangeHandlerFactory(requestRouterCopy, handler -> new BasicAsyncServerExpectationDecorator(handler, exceptionCallback)); } final ServerHttp1StreamDuplexerFactory streamHandlerFactory = new ServerHttp1StreamDuplexerFactory( httpProcessor != null ? httpProcessor : HttpProcessors.server(), handlerFactory, http1Config, charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT, connStrategy != null ? connStrategy : DefaultConnectionReuseStrategy.INSTANCE, new DefaultHttpRequestParserFactory(http1Config), new DefaultHttpResponseWriterFactory(http1Config), DefaultContentLengthStrategy.INSTANCE, DefaultContentLengthStrategy.INSTANCE, streamListener, exceptionCallback); final IOEventHandlerFactory ioEventHandlerFactory = new ServerHttp1IOEventHandlerFactory( streamHandlerFactory, tlsStrategy, handshakeTimeout); return new HttpAsyncServer(ioEventHandlerFactory, ioReactorConfig, ioSessionDecorator, exceptionCallback, sessionListener, threadPoolListener, null, null); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/FilterEntry.java0100644 0000000 0000000 00000003307 00000250600 026625 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.bootstrap; final class FilterEntry { enum Position { BEFORE, AFTER, REPLACE, FIRST, LAST } final Position position; final String name; final T filterHandler; final String existing; FilterEntry( final Position position, final String name, final T filterHandler, final String existing) { this.position = position; this.name = name; this.filterHandler = filterHandler; this.existing = existing; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpAsyncRequester.java0100644 0000000 0000000 00000055052 00000250600 030177 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.bootstrap; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.Set; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.concurrent.BasicFuture; import org.apache.hc.core5.concurrent.CallbackContribution; import org.apache.hc.core5.concurrent.CompletingFutureContribution; import org.apache.hc.core5.concurrent.ComplexFuture; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.impl.DefaultAddressResolver; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.AsyncRequestProducer; import org.apache.hc.core5.http.nio.AsyncResponseConsumer; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.nio.RequestChannel; import org.apache.hc.core5.http.nio.command.RequestExecutionCommand; import org.apache.hc.core5.http.nio.command.ShutdownCommand; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.http.nio.ssl.TlsUpgradeCapable; import org.apache.hc.core5.http.nio.support.BasicClientExchangeHandler; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.pool.ConnPoolControl; import org.apache.hc.core5.pool.ManagedConnPool; import org.apache.hc.core5.pool.PoolEntry; import org.apache.hc.core5.pool.PoolStats; import org.apache.hc.core5.reactor.Command; import org.apache.hc.core5.reactor.EndpointParameters; import org.apache.hc.core5.reactor.IOEventHandler; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.IOReactorMetricsListener; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.IOSessionListener; import org.apache.hc.core5.reactor.IOWorkerSelector; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.reactor.ssl.TransportSecurityLayer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; /** * HTTP/1.1 client side message exchange initiator. * * @since 5.0 */ public class HttpAsyncRequester extends AsyncRequester implements ConnPoolControl { private final ManagedConnPool connPool; private final TlsStrategy tlsStrategy; private final Timeout handshakeTimeout; /** * Use {@link AsyncRequesterBootstrap} to create instances of this class. * * @since 5.2 */ @Internal public HttpAsyncRequester( final IOReactorConfig ioReactorConfig, final IOEventHandlerFactory eventHandlerFactory, final Decorator ioSessionDecorator, final Callback exceptionCallback, final IOSessionListener sessionListener, final ManagedConnPool connPool, final TlsStrategy tlsStrategy, final Timeout handshakeTimeout, final IOReactorMetricsListener threadPoolListener, final IOWorkerSelector workerSelector) { super(eventHandlerFactory, ioReactorConfig, ioSessionDecorator, exceptionCallback, sessionListener, ShutdownCommand.GRACEFUL_IMMEDIATE_CALLBACK, DefaultAddressResolver.INSTANCE, threadPoolListener, workerSelector); this.connPool = Args.notNull(connPool, "Connection pool"); this.tlsStrategy = tlsStrategy; this.handshakeTimeout = handshakeTimeout; } @Override public PoolStats getTotalStats() { return connPool.getTotalStats(); } @Override public PoolStats getStats(final HttpHost route) { return connPool.getStats(route); } @Override public void setMaxTotal(final int max) { connPool.setMaxTotal(max); } @Override public int getMaxTotal() { return connPool.getMaxTotal(); } @Override public void setDefaultMaxPerRoute(final int max) { connPool.setDefaultMaxPerRoute(max); } @Override public int getDefaultMaxPerRoute() { return connPool.getDefaultMaxPerRoute(); } @Override public void setMaxPerRoute(final HttpHost route, final int max) { connPool.setMaxPerRoute(route, max); } @Override public int getMaxPerRoute(final HttpHost route) { return connPool.getMaxPerRoute(route); } @Override public void closeIdle(final TimeValue idleTime) { connPool.closeIdle(idleTime); } @Override public void closeExpired() { connPool.closeExpired(); } @Override public Set getRoutes() { return connPool.getRoutes(); } public Future connect( final HttpHost host, final Timeout timeout, final Object attachment, final FutureCallback callback) { return doConnect(host, timeout, attachment, callback); } protected Future doConnect( final HttpHost host, final Timeout timeout, final Object attachment, final FutureCallback callback) { Args.notNull(host, "Host"); Args.notNull(timeout, "Timeout"); final ComplexFuture resultFuture = new ComplexFuture<>(callback); final Future> leaseFuture = connPool.lease( host, null, timeout, new FutureCallback>() { @Override public void completed(final PoolEntry poolEntry) { final AsyncClientEndpoint endpoint = new InternalAsyncClientEndpoint(poolEntry); final IOSession ioSession = poolEntry.getConnection(); if (ioSession != null && !ioSession.isOpen()) { poolEntry.discardConnection(CloseMode.IMMEDIATE); } if (poolEntry.hasConnection()) { resultFuture.completed(endpoint); } else { final Future future = requestSession( host, timeout, new EndpointParameters(host, attachment), new FutureCallback() { @Override public void completed(final IOSession session) { session.setSocketTimeout(timeout); poolEntry.assignConnection(session); resultFuture.completed(endpoint); } @Override public void failed(final Exception cause) { try { resultFuture.failed(cause); } finally { endpoint.releaseAndDiscard(); } } @Override public void cancelled() { try { resultFuture.cancel(); } finally { endpoint.releaseAndDiscard(); } } }); resultFuture.setDependency(future); } } @Override public void failed(final Exception ex) { resultFuture.failed(ex); } @Override public void cancelled() { resultFuture.cancel(); } }); resultFuture.setDependency(leaseFuture); return resultFuture; } public Future connect(final HttpHost host, final Timeout timeout) { return connect(host, timeout, null, null); } /** * @since 5.3 */ public void execute( final HttpHost target, final AsyncClientExchangeHandler exchangeHandler, final HandlerFactory pushHandlerFactory, final Timeout timeout, final HttpContext executeContext) { Args.notNull(exchangeHandler, "Exchange handler"); Args.notNull(timeout, "Timeout"); Args.notNull(executeContext, "Context"); try { exchangeHandler.produceRequest((request, entityDetails, requestContext) -> { final HttpHost host = target != null ? target : defaultTarget(request); if (request.getAuthority() == null) { request.setAuthority(new URIAuthority(host)); } connect(host, timeout, null, new FutureCallback() { @Override public void completed(final AsyncClientEndpoint endpoint) { endpoint.execute(new AsyncClientExchangeHandler() { @Override public void releaseResources() { endpoint.releaseAndDiscard(); exchangeHandler.releaseResources(); } @Override public void failed(final Exception cause) { endpoint.releaseAndDiscard(); exchangeHandler.failed(cause); } @Override public void cancel() { endpoint.releaseAndDiscard(); exchangeHandler.cancel(); } @Override public void produceRequest(final RequestChannel channel, final HttpContext httpContext) throws HttpException, IOException { channel.sendRequest(request, entityDetails, httpContext); } @Override public int available() { return exchangeHandler.available(); } @Override public void produce(final DataStreamChannel channel) throws IOException { exchangeHandler.produce(channel); } @Override public void consumeInformation(final HttpResponse response, final HttpContext httpContext) throws HttpException, IOException { exchangeHandler.consumeInformation(response, httpContext); } @Override public void consumeResponse( final HttpResponse response, final EntityDetails entityDetails, final HttpContext httpContext) throws HttpException, IOException { if (entityDetails == null) { endpoint.releaseAndReuse(); } exchangeHandler.consumeResponse(response, entityDetails, httpContext); } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { exchangeHandler.updateCapacity(capacityChannel); } @Override public void consume(final ByteBuffer src) throws IOException { exchangeHandler.consume(src); } @Override public void streamEnd(final List trailers) throws HttpException, IOException { endpoint.releaseAndReuse(); exchangeHandler.streamEnd(trailers); } }, pushHandlerFactory, executeContext); } @Override public void failed(final Exception ex) { exchangeHandler.failed(ex); } @Override public void cancelled() { exchangeHandler.cancel(); } }); }, executeContext); } catch (final IOException | HttpException ex) { exchangeHandler.failed(ex); } } public void execute( final AsyncClientExchangeHandler exchangeHandler, final HandlerFactory pushHandlerFactory, final Timeout timeout, final HttpContext executeContext) { execute(null, exchangeHandler, pushHandlerFactory, timeout, executeContext); } public void execute( final AsyncClientExchangeHandler exchangeHandler, final Timeout timeout, final HttpContext executeContext) { execute(exchangeHandler, null, timeout, executeContext); } /** * @param The result type returned by the Future's {@code get} method. * @since 5.3 */ public final Future execute( final HttpHost target, final AsyncRequestProducer requestProducer, final AsyncResponseConsumer responseConsumer, final HandlerFactory pushHandlerFactory, final Timeout timeout, final HttpContext context, final FutureCallback callback) { Args.notNull(requestProducer, "Request producer"); Args.notNull(responseConsumer, "Response consumer"); Args.notNull(timeout, "Timeout"); final BasicFuture future = new BasicFuture<>(callback); final AsyncClientExchangeHandler exchangeHandler = new BasicClientExchangeHandler<>( requestProducer, responseConsumer, new CompletingFutureContribution(future)); execute(target, exchangeHandler, pushHandlerFactory, timeout, context != null ? context : HttpCoreContext.create()); return future; } public final Future execute( final AsyncRequestProducer requestProducer, final AsyncResponseConsumer responseConsumer, final HandlerFactory pushHandlerFactory, final Timeout timeout, final HttpContext context, final FutureCallback callback) { return execute(null, requestProducer, responseConsumer, pushHandlerFactory, timeout, context, callback); } public final Future execute( final AsyncRequestProducer requestProducer, final AsyncResponseConsumer responseConsumer, final Timeout timeout, final HttpContext context, final FutureCallback callback) { return execute(requestProducer, responseConsumer, null, timeout, context, callback); } /** * @param The result type returned by the Future's {@code get} method. * @since 5.3 */ public final Future execute( final HttpHost target, final AsyncRequestProducer requestProducer, final AsyncResponseConsumer responseConsumer, final Timeout timeout, final FutureCallback callback) { return execute(target, requestProducer, responseConsumer, null, timeout, null, callback); } public final Future execute( final AsyncRequestProducer requestProducer, final AsyncResponseConsumer responseConsumer, final Timeout timeout, final FutureCallback callback) { return execute(requestProducer, responseConsumer, null, timeout, null, callback); } protected void doTlsUpgrade( final ProtocolIOSession ioSession, final NamedEndpoint endpoint, final FutureCallback callback) { if (tlsStrategy != null) { tlsStrategy.upgrade(ioSession, endpoint, null, handshakeTimeout, new CallbackContribution(callback) { @Override public void completed(final TransportSecurityLayer transportSecurityLayer) { if (callback != null) { callback.completed(ioSession); } } }); } else { throw new IllegalStateException("TLS upgrade not supported"); } } private class InternalAsyncClientEndpoint extends AsyncClientEndpoint implements TlsUpgradeCapable { final AtomicReference> poolEntryRef; InternalAsyncClientEndpoint(final PoolEntry poolEntry) { this.poolEntryRef = new AtomicReference<>(poolEntry); } private IOSession getIOSession() { final PoolEntry poolEntry = poolEntryRef.get(); if (poolEntry == null) { throw new IllegalStateException("Endpoint has already been released"); } final IOSession ioSession = poolEntry.getConnection(); if (ioSession == null) { throw new IllegalStateException("I/O session is invalid"); } return ioSession; } @Override public void execute( final AsyncClientExchangeHandler exchangeHandler, final HandlerFactory pushHandlerFactory, final HttpContext context) { final IOSession ioSession = getIOSession(); ioSession.enqueue(new RequestExecutionCommand(exchangeHandler, pushHandlerFactory, null, context), Command.Priority.NORMAL); if (!ioSession.isOpen()) { try { exchangeHandler.failed(new ConnectionClosedException()); } finally { exchangeHandler.releaseResources(); } } } @Override public boolean isConnected() { final PoolEntry poolEntry = poolEntryRef.get(); if (poolEntry != null) { final IOSession ioSession = poolEntry.getConnection(); if (ioSession == null || !ioSession.isOpen()) { return false; } final IOEventHandler handler = ioSession.getHandler(); return handler instanceof HttpConnection && ((HttpConnection) handler).isOpen(); } return false; } @Override public void releaseAndReuse() { final PoolEntry poolEntry = poolEntryRef.getAndSet(null); if (poolEntry != null) { final IOSession ioSession = poolEntry.getConnection(); connPool.release(poolEntry, ioSession != null && ioSession.isOpen()); } } @Override public void releaseAndDiscard() { final PoolEntry poolEntry = poolEntryRef.getAndSet(null); if (poolEntry != null) { poolEntry.discardConnection(CloseMode.GRACEFUL); connPool.release(poolEntry, false); } } @Override public void tlsUpgrade(final NamedEndpoint endpoint, final FutureCallback callback) { final IOSession ioSession = getIOSession(); if (ioSession instanceof ProtocolIOSession) { doTlsUpgrade((ProtocolIOSession) ioSession, endpoint, callback); } else { throw new IllegalStateException("TLS upgrade not supported"); } } } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpAsyncServer.java0100644 0000000 0000000 00000011202 00000250600 027453 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.bootstrap; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.concurrent.Future; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.nio.command.ShutdownCommand; import org.apache.hc.core5.reactor.EndpointParameters; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.IOReactorMetricsListener; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.IOSessionListener; import org.apache.hc.core5.reactor.IOWorkerSelector; import org.apache.hc.core5.reactor.ListenerEndpoint; /** * HTTP/1.1 server side message exchange handler. * * @since 5.0 */ public class HttpAsyncServer extends AsyncServer { private final String canonicalName; /** * Use {@link AsyncServerBootstrap} to create instances of this class. * * @since 5.1 */ @Internal public HttpAsyncServer( final IOEventHandlerFactory eventHandlerFactory, final IOReactorConfig ioReactorConfig, final Decorator ioSessionDecorator, final Callback exceptionCallback, final IOSessionListener sessionListener, final IOReactorMetricsListener threadPoolListener, final IOWorkerSelector workerSelector, final String canonicalName) { super(eventHandlerFactory, ioReactorConfig, ioSessionDecorator, exceptionCallback, sessionListener, threadPoolListener, ShutdownCommand.GRACEFUL_NORMAL_CALLBACK, workerSelector); this.canonicalName = canonicalName; } /** * @since 5.1 */ public Future listen( final SocketAddress address, final URIScheme scheme, final Object attachment, final FutureCallback callback) { final InetSocketAddress inetSocketAddress = (InetSocketAddress) address; final EndpointParameters parameters = new EndpointParameters( scheme.id, canonicalName != null ? canonicalName : "localhost", inetSocketAddress.getPort(), attachment); return super.listen(address, parameters, callback); } /** * @since 5.1 */ public Future listen( final SocketAddress address, final URIScheme scheme, final FutureCallback callback) { return listen(address, scheme, null, callback); } /** * @since 5.1 */ public Future listen(final SocketAddress address, final URIScheme scheme) { return listen(address, scheme, null, null); } /** * @deprecated Use {@link #listen(SocketAddress, URIScheme, FutureCallback)} */ @Deprecated @Override public Future listen(final SocketAddress address, final FutureCallback callback) { return super.listen(address, callback); } /** * @deprecated Use {@link #listen(SocketAddress, URIScheme)} */ @Deprecated @Override public Future listen(final SocketAddress address) { return super.listen(address); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpRequester.java0100644 0000000 0000000 00000050514 00000250600 027177 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.bootstrap; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.Socket; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import jdk.net.ExtendedSocketOptions; import jdk.net.Sockets; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.function.Resolver; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.ConnectionRequestTimeoutException; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.DefaultAddressResolver; import org.apache.hc.core5.http.impl.io.DefaultBHttpClientConnectionFactory; import org.apache.hc.core5.http.impl.io.HttpRequestExecutor; import org.apache.hc.core5.http.io.EofSensorInputStream; import org.apache.hc.core5.http.io.EofSensorWatcher; import org.apache.hc.core5.http.io.HttpClientConnection; import org.apache.hc.core5.http.io.HttpClientResponseHandler; import org.apache.hc.core5.http.io.HttpConnectionFactory; import org.apache.hc.core5.http.io.HttpResponseInformationCallback; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.HttpEntityWrapper; import org.apache.hc.core5.http.io.ssl.SSLSessionVerifier; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.Closer; import org.apache.hc.core5.io.ModalCloseable; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.pool.ConnPoolControl; import org.apache.hc.core5.pool.ManagedConnPool; import org.apache.hc.core5.pool.PoolEntry; import org.apache.hc.core5.pool.PoolStats; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.ReflectionUtils; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; /** * HTTP/1.1 client side message exchange initiator. * * @since 5.0 */ public class HttpRequester implements ConnPoolControl, ModalCloseable { private final HttpRequestExecutor requestExecutor; private final HttpProcessor httpProcessor; private final ManagedConnPool connPool; private final SocketConfig socketConfig; private final HttpConnectionFactory connectFactory; private final SSLSocketFactory sslSocketFactory; private final Callback sslSetupHandler; private final SSLSessionVerifier sslSessionVerifier; private final Resolver addressResolver; /** * Use {@link RequesterBootstrap} to create instances of this class. */ @Internal public HttpRequester( final HttpRequestExecutor requestExecutor, final HttpProcessor httpProcessor, final ManagedConnPool connPool, final SocketConfig socketConfig, final HttpConnectionFactory connectFactory, final SSLSocketFactory sslSocketFactory, final Callback sslSetupHandler, final SSLSessionVerifier sslSessionVerifier, final Resolver addressResolver) { this.requestExecutor = Args.notNull(requestExecutor, "Request executor"); this.httpProcessor = Args.notNull(httpProcessor, "HTTP processor"); this.connPool = Args.notNull(connPool, "Connection pool"); this.socketConfig = socketConfig != null ? socketConfig : SocketConfig.DEFAULT; this.connectFactory = connectFactory != null ? connectFactory : new DefaultBHttpClientConnectionFactory( Http1Config.DEFAULT, CharCodingConfig.DEFAULT); this.sslSocketFactory = sslSocketFactory != null ? sslSocketFactory : (SSLSocketFactory) SSLSocketFactory.getDefault(); this.sslSetupHandler = sslSetupHandler; this.sslSessionVerifier = sslSessionVerifier; this.addressResolver = addressResolver != null ? addressResolver : DefaultAddressResolver.INSTANCE; } @Override public PoolStats getTotalStats() { return connPool.getTotalStats(); } @Override public PoolStats getStats(final HttpHost route) { return connPool.getStats(route); } @Override public void setMaxTotal(final int max) { connPool.setMaxTotal(max); } @Override public int getMaxTotal() { return connPool.getMaxTotal(); } @Override public void setDefaultMaxPerRoute(final int max) { connPool.setDefaultMaxPerRoute(max); } @Override public int getDefaultMaxPerRoute() { return connPool.getDefaultMaxPerRoute(); } @Override public void setMaxPerRoute(final HttpHost route, final int max) { connPool.setMaxPerRoute(route, max); } @Override public int getMaxPerRoute(final HttpHost route) { return connPool.getMaxPerRoute(route); } @Override public void closeIdle(final TimeValue idleTime) { connPool.closeIdle(idleTime); } @Override public void closeExpired() { connPool.closeExpired(); } @Override public Set getRoutes() { return connPool.getRoutes(); } public ClassicHttpResponse execute( final HttpClientConnection connection, final ClassicHttpRequest request, final HttpResponseInformationCallback informationCallback, final HttpContext context) throws HttpException, IOException { Args.notNull(connection, "HTTP connection"); Args.notNull(request, "HTTP request"); Args.notNull(context, "HTTP context"); if (!connection.isOpen()) { throw new ConnectionClosedException(); } requestExecutor.preProcess(request, httpProcessor, context); final ClassicHttpResponse response = requestExecutor.execute(request, connection, informationCallback, context); requestExecutor.postProcess(response, httpProcessor, context); return response; } public ClassicHttpResponse execute( final HttpClientConnection connection, final ClassicHttpRequest request, final HttpContext context) throws HttpException, IOException { return execute(connection, request, null, context); } public boolean keepAlive( final HttpClientConnection connection, final ClassicHttpRequest request, final ClassicHttpResponse response, final HttpContext context) throws IOException { final boolean keepAlive = requestExecutor.keepAlive(request, response, connection, context); if (!keepAlive) { connection.close(); } return keepAlive; } public T execute( final HttpClientConnection connection, final ClassicHttpRequest request, final HttpContext context, final HttpClientResponseHandler responseHandler) throws HttpException, IOException { try (final ClassicHttpResponse response = execute(connection, request, context)) { final T result = responseHandler.handleResponse(response); EntityUtils.consume(response.getEntity()); final boolean keepAlive = requestExecutor.keepAlive(request, response, connection, context); if (!keepAlive) { connection.close(); } return result; } catch (final HttpException | IOException | RuntimeException ex) { connection.close(CloseMode.IMMEDIATE); throw ex; } } @SuppressWarnings("Since15") private HttpClientConnection createConnection(final Socket sock, final HttpHost targetHost) throws IOException { sock.setSoTimeout(socketConfig.getSoTimeout().toMillisecondsIntBound()); sock.setReuseAddress(socketConfig.isSoReuseAddress()); sock.setTcpNoDelay(socketConfig.isTcpNoDelay()); sock.setKeepAlive(socketConfig.isSoKeepAlive()); if (socketConfig.getRcvBufSize() > 0) { sock.setReceiveBufferSize(socketConfig.getRcvBufSize()); } if (socketConfig.getSndBufSize() > 0) { sock.setSendBufferSize(socketConfig.getSndBufSize()); } if (ReflectionUtils.supportsKeepAliveOptions()) { if (this.socketConfig.getTcpKeepIdle() > 0) { Sockets.setOption(sock, ExtendedSocketOptions.TCP_KEEPIDLE, this.socketConfig.getTcpKeepIdle()); } if (this.socketConfig.getTcpKeepInterval() > 0) { Sockets.setOption(sock, ExtendedSocketOptions.TCP_KEEPINTERVAL, this.socketConfig.getTcpKeepInterval()); } if (this.socketConfig.getTcpKeepCount() > 0) { Sockets.setOption(sock, ExtendedSocketOptions.TCP_KEEPCOUNT, this.socketConfig.getTcpKeepCount()); } } final int linger = socketConfig.getSoLinger().toMillisecondsIntBound(); if (linger >= 0) { sock.setSoLinger(true, linger); } final InetSocketAddress targetAddress = addressResolver.resolve(targetHost); sock.connect(targetAddress, socketConfig.getSoTimeout().toMillisecondsIntBound()); if (URIScheme.HTTPS.same(targetHost.getSchemeName())) { final SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket( sock, targetHost.getHostName(), targetAddress.getPort(), false); if (this.sslSetupHandler != null) { final SSLParameters sslParameters = sslSocket.getSSLParameters(); this.sslSetupHandler.execute(sslParameters); sslSocket.setSSLParameters(sslParameters); } try { sslSocket.startHandshake(); final SSLSession session = sslSocket.getSession(); if (session == null) { throw new SSLHandshakeException("SSL session not available"); } if (sslSessionVerifier != null) { sslSessionVerifier.verify(targetHost, session); } return connectFactory.createConnection(sslSocket, sock); } catch (final IOException ex) { Closer.closeQuietly(sslSocket); throw ex; } } return connectFactory.createConnection(sock); } public ClassicHttpResponse execute( final HttpHost targetHost, final ClassicHttpRequest request, final HttpResponseInformationCallback informationCallback, final Timeout connectTimeout, final HttpContext context) throws HttpException, IOException { Args.notNull(targetHost, "HTTP host"); Args.notNull(request, "HTTP request"); final Future> leaseFuture = connPool.lease(targetHost, null, connectTimeout, null); final PoolEntry poolEntry; final Timeout timeout = Timeout.defaultsToInfinite(connectTimeout); try { poolEntry = leaseFuture.get(timeout.getDuration(), timeout.getTimeUnit()); } catch (final InterruptedException ex) { Thread.currentThread().interrupt(); throw new InterruptedIOException(ex.getMessage()); } catch (final ExecutionException ex) { throw new HttpException("Unexpected failure leasing connection", ex); } catch (final TimeoutException ex) { throw new ConnectionRequestTimeoutException("Connection request timeout"); } final PoolEntryHolder connectionHolder = new PoolEntryHolder(poolEntry); try { HttpClientConnection connection = poolEntry.getConnection(); if (connection == null) { final Socket sock; if (socketConfig.getSocksProxyAddress() != null) { sock = new Socket(new Proxy(Proxy.Type.SOCKS, socketConfig.getSocksProxyAddress())); } else { sock = new Socket(); } try { connection = createConnection(sock, targetHost); poolEntry.assignConnection(connection); } catch (IOException | RuntimeException ex) { Closer.closeQuietly(sock); throw ex; } } if (request.getAuthority() == null) { request.setAuthority(new URIAuthority(targetHost)); } final ClassicHttpResponse response = execute(connection, request, informationCallback, context); final HttpEntity entity = response.getEntity(); if (entity != null) { response.setEntity(new HttpEntityWrapper(entity) { private void releaseConnection() throws IOException { try { final HttpClientConnection localConn = connectionHolder.getConnection(); if (localConn != null) { if (requestExecutor.keepAlive(request, response, localConn, context)) { if (super.isStreaming()) { Closer.close(super.getContent()); } connectionHolder.releaseConnection(); } } } finally { connectionHolder.discardConnection(); } } private void abortConnection() { connectionHolder.discardConnection(); } @Override public boolean isStreaming() { return true; } @Override public InputStream getContent() throws IOException { return new EofSensorInputStream(super.getContent(), new EofSensorWatcher() { @Override public boolean eofDetected(final InputStream wrapped) throws IOException { releaseConnection(); return false; } @Override public boolean streamClosed(final InputStream wrapped) throws IOException { releaseConnection(); return false; } @Override public boolean streamAbort(final InputStream wrapped) throws IOException { abortConnection(); return false; } }); } @Override public void writeTo(final OutputStream outStream) throws IOException { try { if (outStream != null) { super.writeTo(outStream); } close(); } catch (final IOException | RuntimeException ex) { abortConnection(); } } @Override public void close() throws IOException { releaseConnection(); } }); } else { final HttpClientConnection localConn = connectionHolder.getConnection(); if (!requestExecutor.keepAlive(request, response, localConn, context)) { localConn.close(); } connectionHolder.releaseConnection(); } return response; } catch (final HttpException | IOException | RuntimeException ex) { connectionHolder.discardConnection(); throw ex; } } public ClassicHttpResponse execute( final HttpHost targetHost, final ClassicHttpRequest request, final Timeout connectTimeout, final HttpContext context) throws HttpException, IOException { return execute(targetHost, request, null, connectTimeout, context); } public T execute( final HttpHost targetHost, final ClassicHttpRequest request, final Timeout connectTimeout, final HttpContext context, final HttpClientResponseHandler responseHandler) throws HttpException, IOException { try (final ClassicHttpResponse response = execute(targetHost, request, null, connectTimeout, context)) { final T result = responseHandler.handleResponse(response); EntityUtils.consume(response.getEntity()); return result; } } public ConnPoolControl getConnPoolControl() { return connPool; } @Override public void close(final CloseMode closeMode) { connPool.close(closeMode); } @Override public void close() throws IOException { connPool.close(); } private class PoolEntryHolder { private final AtomicReference> poolEntryRef; PoolEntryHolder(final PoolEntry poolEntry) { this.poolEntryRef = new AtomicReference<>(poolEntry); } HttpClientConnection getConnection() { final PoolEntry poolEntry = poolEntryRef.get(); return poolEntry != null ? poolEntry.getConnection() : null; } void releaseConnection() { final PoolEntry poolEntry = poolEntryRef.getAndSet(null); if (poolEntry != null) { final HttpClientConnection connection = poolEntry.getConnection(); connPool.release(poolEntry, connection != null && connection.isOpen()); } } void discardConnection() { final PoolEntry poolEntry = poolEntryRef.getAndSet(null); if (poolEntry != null) { poolEntry.discardConnection(CloseMode.GRACEFUL); connPool.release(poolEntry, false); } } } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpServer.java0100644 0000000 0000000 00000024163 00000250600 026467 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.bootstrap; import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.util.Set; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.net.ServerSocketFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import jdk.net.ExtendedSocketOptions; import jdk.net.Sockets; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.concurrent.DefaultThreadFactory; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.ExceptionListener; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.io.DefaultBHttpServerConnection; import org.apache.hc.core5.http.impl.io.DefaultBHttpServerConnectionFactory; import org.apache.hc.core5.http.impl.io.HttpService; import org.apache.hc.core5.http.io.HttpConnectionFactory; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.Closer; import org.apache.hc.core5.io.ModalCloseable; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.ReflectionUtils; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; /** * HTTP/1.1 server side message exchange handler. * * @since 4.4 */ public class HttpServer implements ModalCloseable { enum Status { READY, ACTIVE, STOPPING } private final int port; private final InetAddress ifAddress; private final SocketConfig socketConfig; private final ServerSocketFactory serverSocketFactory; private final HttpService httpService; private final HttpConnectionFactory connectionFactory; private final SSLContext sslContext; private final Callback sslSetupHandler; private final ExceptionListener exceptionListener; private final ThreadPoolExecutor listenerExecutorService; private final ThreadGroup workerThreads; private final WorkerPoolExecutor workerExecutorService; private final AtomicReference status; private volatile ServerSocket serverSocket; private volatile RequestListener requestListener; @Internal public HttpServer( final int port, final HttpService httpService, final InetAddress ifAddress, final SocketConfig socketConfig, final ServerSocketFactory serverSocketFactory, final HttpConnectionFactory connectionFactory, final SSLContext sslContext, final Callback sslSetupHandler, final ExceptionListener exceptionListener) { this.port = Args.notNegative(port, "Port value is negative"); this.httpService = Args.notNull(httpService, "HTTP service"); this.ifAddress = ifAddress; this.socketConfig = socketConfig != null ? socketConfig : SocketConfig.DEFAULT; this.serverSocketFactory = serverSocketFactory != null ? serverSocketFactory : ServerSocketFactory.getDefault(); this.connectionFactory = connectionFactory != null ? connectionFactory : new DefaultBHttpServerConnectionFactory( this.serverSocketFactory instanceof SSLServerSocketFactory ? URIScheme.HTTPS.id : URIScheme.HTTP.id, Http1Config.DEFAULT, CharCodingConfig.DEFAULT); this.sslContext = sslContext; this.sslSetupHandler = sslSetupHandler; this.exceptionListener = exceptionListener != null ? exceptionListener : ExceptionListener.NO_OP; this.listenerExecutorService = new ThreadPoolExecutor( 1, 1, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<>(), new DefaultThreadFactory("HTTP-listener-" + this.port)); this.workerThreads = new ThreadGroup("HTTP-workers"); this.workerExecutorService = new WorkerPoolExecutor( 0, Integer.MAX_VALUE, 1L, TimeUnit.SECONDS, new SynchronousQueue<>(), new DefaultThreadFactory("HTTP-worker", this.workerThreads, true)); this.status = new AtomicReference<>(Status.READY); } public InetAddress getInetAddress() { final ServerSocket localSocket = this.serverSocket; if (localSocket != null) { return localSocket.getInetAddress(); } return null; } public int getLocalPort() { final ServerSocket localSocket = this.serverSocket; if (localSocket != null) { return localSocket.getLocalPort(); } return -1; } @SuppressWarnings("Since15") public void start() throws IOException { if (this.status.compareAndSet(Status.READY, Status.ACTIVE)) { this.serverSocket = this.serverSocketFactory.createServerSocket( this.port, this.socketConfig.getBacklogSize(), this.ifAddress); this.serverSocket.setReuseAddress(this.socketConfig.isSoReuseAddress()); if (this.socketConfig.getRcvBufSize() > 0) { this.serverSocket.setReceiveBufferSize(this.socketConfig.getRcvBufSize()); } if (ReflectionUtils.supportsKeepAliveOptions()) { if (this.socketConfig.getTcpKeepIdle() > 0) { Sockets.setOption(this.serverSocket, ExtendedSocketOptions.TCP_KEEPIDLE, this.socketConfig.getTcpKeepIdle()); } if (this.socketConfig.getTcpKeepInterval() > 0) { Sockets.setOption(this.serverSocket, ExtendedSocketOptions.TCP_KEEPINTERVAL, this.socketConfig.getTcpKeepInterval()); } if (this.socketConfig.getTcpKeepCount() > 0) { Sockets.setOption(this.serverSocket, ExtendedSocketOptions.TCP_KEEPCOUNT, this.socketConfig.getTcpKeepCount()); } } if (this.sslSetupHandler != null && this.serverSocket instanceof SSLServerSocket) { final SSLServerSocket sslServerSocket = (SSLServerSocket) this.serverSocket; final SSLParameters sslParameters = sslServerSocket.getSSLParameters(); this.sslSetupHandler.execute(sslParameters); sslServerSocket.setSSLParameters(sslParameters); } this.requestListener = new RequestListener( this.socketConfig, this.serverSocket, this.httpService, this.connectionFactory, this.sslContext != null ? this.sslContext.getSocketFactory() : null, this.sslSetupHandler, this.exceptionListener, this.workerExecutorService); this.listenerExecutorService.execute(this.requestListener); } } public void stop() { if (this.status.compareAndSet(Status.ACTIVE, Status.STOPPING)) { this.listenerExecutorService.shutdownNow(); this.workerExecutorService.shutdown(); final RequestListener local = this.requestListener; if (local != null) { try { local.terminate(); } catch (final IOException ex) { this.exceptionListener.onError(ex); } } this.workerThreads.interrupt(); } } public void initiateShutdown() { stop(); } public void awaitTermination(final TimeValue waitTime) throws InterruptedException { Args.notNull(waitTime, "Wait time"); this.workerExecutorService.awaitTermination(waitTime.getDuration(), waitTime.getTimeUnit()); } @Override public void close(final CloseMode closeMode) { close(closeMode, Timeout.ofSeconds(5)); } /** * Closes this process or endpoint and releases any system resources associated * with it. If the endpoint or the process is already closed then invoking this * method has no effect. * * @param closeMode How to close the receiver. * @param timeout How long to wait for the HttpServer to close gracefully. * @since 5.2 */ public void close(final CloseMode closeMode, final Timeout timeout) { initiateShutdown(); if (closeMode == CloseMode.GRACEFUL) { try { awaitTermination(timeout); } catch (final InterruptedException ex) { Thread.currentThread().interrupt(); } } final Set workers = this.workerExecutorService.getWorkers(); for (final Worker worker: workers) { Closer.close(worker.getConnection(), CloseMode.GRACEFUL); } } @Override public void close() { close(CloseMode.GRACEFUL); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/RequestListener.java0100644 0000000 0000000 00000015537 00000250600 027524 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.bootstrap; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicBoolean; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import jdk.net.ExtendedSocketOptions; import jdk.net.Sockets; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.ExceptionListener; import org.apache.hc.core5.http.impl.io.HttpService; import org.apache.hc.core5.http.io.HttpConnectionFactory; import org.apache.hc.core5.http.io.HttpServerConnection; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.io.Closer; import org.apache.hc.core5.util.ReflectionUtils; class RequestListener implements Runnable { private final SocketConfig socketConfig; private final ServerSocket serverSocket; private final HttpService httpService; private final HttpConnectionFactory connectionFactory; private final SSLSocketFactory sslSocketFactory; private final Callback sslSetupHandler; private final ExceptionListener exceptionListener; private final ExecutorService executorService; private final AtomicBoolean terminated; public RequestListener( final SocketConfig socketConfig, final ServerSocket serversocket, final HttpService httpService, final HttpConnectionFactory connectionFactory, final SSLSocketFactory sslSocketFactory, final Callback sslSetupHandler, final ExceptionListener exceptionListener, final ExecutorService executorService) { this.socketConfig = socketConfig; this.serverSocket = serversocket; this.httpService = httpService; this.connectionFactory = connectionFactory; this.sslSocketFactory = sslSocketFactory; this.sslSetupHandler = sslSetupHandler; this.exceptionListener = exceptionListener; this.executorService = executorService; this.terminated = new AtomicBoolean(); } @SuppressWarnings("Since15") private HttpServerConnection createConnection(final Socket socket) throws IOException { socket.setSoTimeout(this.socketConfig.getSoTimeout().toMillisecondsIntBound()); socket.setKeepAlive(this.socketConfig.isSoKeepAlive()); socket.setTcpNoDelay(this.socketConfig.isTcpNoDelay()); if (this.socketConfig.getRcvBufSize() > 0) { socket.setReceiveBufferSize(this.socketConfig.getRcvBufSize()); } if (this.socketConfig.getSndBufSize() > 0) { socket.setSendBufferSize(this.socketConfig.getSndBufSize()); } if (this.socketConfig.getSoLinger().toSeconds() >= 0) { socket.setSoLinger(true, this.socketConfig.getSoLinger().toSecondsIntBound()); } if (ReflectionUtils.supportsKeepAliveOptions()) { if (this.socketConfig.getTcpKeepIdle() > 0) { Sockets.setOption(this.serverSocket, ExtendedSocketOptions.TCP_KEEPIDLE, this.socketConfig.getTcpKeepIdle()); } if (this.socketConfig.getTcpKeepInterval() > 0) { Sockets.setOption(this.serverSocket, ExtendedSocketOptions.TCP_KEEPINTERVAL, this.socketConfig.getTcpKeepInterval()); } if (this.socketConfig.getTcpKeepCount() > 0) { Sockets.setOption(this.serverSocket, ExtendedSocketOptions.TCP_KEEPCOUNT, this.socketConfig.getTcpKeepCount()); } } if (!(socket instanceof SSLSocket) && sslSocketFactory != null) { final SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, null, -1, false); sslSocket.setUseClientMode(false); if (this.sslSetupHandler != null) { final SSLParameters sslParameters = sslSocket.getSSLParameters(); this.sslSetupHandler.execute(sslParameters); sslSocket.setSSLParameters(sslParameters); } try { sslSocket.startHandshake(); final SSLSession session = sslSocket.getSession(); if (session == null) { throw new SSLHandshakeException("SSL session not available"); } return this.connectionFactory.createConnection(sslSocket, socket); } catch (final IOException ex) { Closer.closeQuietly(sslSocket); throw ex; } } return this.connectionFactory.createConnection(socket); } @Override public void run() { try { while (!isTerminated() && !Thread.interrupted()) { final Socket socket = this.serverSocket.accept(); try { final HttpServerConnection conn = createConnection(socket); final Worker worker = new Worker(this.httpService, conn, this.exceptionListener); this.executorService.execute(worker); } catch (final IOException | RuntimeException ex) { Closer.closeQuietly(socket); this.exceptionListener.onError(ex); } } } catch (final Exception ex) { this.exceptionListener.onError(ex); } } public boolean isTerminated() { return this.terminated.get(); } public void terminate() throws IOException { if (this.terminated.compareAndSet(false, true)) { this.serverSocket.close(); } } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/RequesterBootstrap.java0100644 0000000 0000000 00000022273 00000250600 030236 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.bootstrap; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSocketFactory; import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.DefaultAddressResolver; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.HttpProcessors; import org.apache.hc.core5.http.impl.io.DefaultBHttpClientConnectionFactory; import org.apache.hc.core5.http.impl.io.HttpRequestExecutor; import org.apache.hc.core5.http.io.HttpClientConnection; import org.apache.hc.core5.http.io.HttpConnectionFactory; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.ssl.DefaultTlsSetupHandler; import org.apache.hc.core5.http.io.ssl.SSLSessionVerifier; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.pool.ConnPoolListener; import org.apache.hc.core5.pool.DefaultDisposalCallback; import org.apache.hc.core5.pool.LaxConnPool; import org.apache.hc.core5.pool.ManagedConnPool; import org.apache.hc.core5.pool.PoolConcurrencyPolicy; import org.apache.hc.core5.pool.PoolReusePolicy; import org.apache.hc.core5.pool.RouteSegmentedConnPool; import org.apache.hc.core5.pool.StrictConnPool; import org.apache.hc.core5.util.Timeout; /** * {@link HttpRequester} bootstrap. * * @since 5.0 */ public class RequesterBootstrap { private HttpProcessor httpProcessor; private Http1Config http1Config; private ConnectionReuseStrategy connReuseStrategy; private SocketConfig socketConfig; private HttpConnectionFactory connectFactory; private SSLSocketFactory sslSocketFactory; private Callback sslSetupHandler; private SSLSessionVerifier sslSessionVerifier; private int defaultMaxPerRoute; private int maxTotal; private Timeout timeToLive; private PoolReusePolicy poolReusePolicy; private PoolConcurrencyPolicy poolConcurrencyPolicy; private Http1StreamListener streamListener; private ConnPoolListener connPoolListener; private RequesterBootstrap() { } public static RequesterBootstrap bootstrap() { return new RequesterBootstrap(); } /** * Sets {@link HttpProcessor} instance. * * @return this instance. */ public final RequesterBootstrap setHttpProcessor(final HttpProcessor httpProcessor) { this.httpProcessor = httpProcessor; return this; } /** * Sets HTTP/1 protocol configuration. * * @return this instance. */ public final RequesterBootstrap setHttp1Config(final Http1Config http1Config) { this.http1Config = http1Config; return this; } /** * Sets {@link ConnectionReuseStrategy} instance. * * @return this instance. */ public final RequesterBootstrap setConnectionReuseStrategy(final ConnectionReuseStrategy connStrategy) { this.connReuseStrategy = connStrategy; return this; } /** * Sets socket configuration. * * @return this instance. */ public final RequesterBootstrap setSocketConfig(final SocketConfig socketConfig) { this.socketConfig = socketConfig; return this; } public final RequesterBootstrap setConnectionFactory(final HttpConnectionFactory connectFactory) { this.connectFactory = connectFactory; return this; } public final RequesterBootstrap setSslContext(final SSLContext sslContext) { this.sslSocketFactory = sslContext != null ? sslContext.getSocketFactory() : null; return this; } public final RequesterBootstrap setSslSocketFactory(final SSLSocketFactory sslSocketFactory) { this.sslSocketFactory = sslSocketFactory; return this; } /** * Sets {@link Callback} for {@link SSLParameters}. * * @return this instance. */ public final RequesterBootstrap setSslSetupHandler(final Callback sslSetupHandler) { this.sslSetupHandler = sslSetupHandler; return this; } /** * Sets {@link SSLSessionVerifier} instance. * * @return this instance. */ public final RequesterBootstrap setSslSessionVerifier(final SSLSessionVerifier sslSessionVerifier) { this.sslSessionVerifier = sslSessionVerifier; return this; } public final RequesterBootstrap setDefaultMaxPerRoute(final int defaultMaxPerRoute) { this.defaultMaxPerRoute = defaultMaxPerRoute; return this; } public final RequesterBootstrap setMaxTotal(final int maxTotal) { this.maxTotal = maxTotal; return this; } public final RequesterBootstrap setTimeToLive(final Timeout timeToLive) { this.timeToLive = timeToLive; return this; } public final RequesterBootstrap setPoolReusePolicy(final PoolReusePolicy poolReusePolicy) { this.poolReusePolicy = poolReusePolicy; return this; } @Experimental public final RequesterBootstrap setPoolConcurrencyPolicy(final PoolConcurrencyPolicy poolConcurrencyPolicy) { this.poolConcurrencyPolicy = poolConcurrencyPolicy; return this; } public final RequesterBootstrap setStreamListener(final Http1StreamListener streamListener) { this.streamListener = streamListener; return this; } public final RequesterBootstrap setConnPoolListener(final ConnPoolListener connPoolListener) { this.connPoolListener = connPoolListener; return this; } public HttpRequester create() { final HttpRequestExecutor requestExecutor = new HttpRequestExecutor( http1Config, connReuseStrategy != null ? connReuseStrategy : DefaultConnectionReuseStrategy.INSTANCE, streamListener); final ManagedConnPool connPool; switch (poolConcurrencyPolicy != null ? poolConcurrencyPolicy : PoolConcurrencyPolicy.STRICT) { case LAX: connPool = new LaxConnPool<>( defaultMaxPerRoute > 0 ? defaultMaxPerRoute : 20, timeToLive, poolReusePolicy, new DefaultDisposalCallback<>(), connPoolListener); break; case OFFLOCK: connPool = new RouteSegmentedConnPool<>( defaultMaxPerRoute > 0 ? defaultMaxPerRoute : 20, maxTotal > 0 ? maxTotal : 50, timeToLive, poolReusePolicy, new DefaultDisposalCallback<>()); break; case STRICT: default: connPool = new StrictConnPool<>( defaultMaxPerRoute > 0 ? defaultMaxPerRoute : 20, maxTotal > 0 ? maxTotal : 50, timeToLive, poolReusePolicy, new DefaultDisposalCallback<>(), connPoolListener); break; } return new HttpRequester( requestExecutor, httpProcessor != null ? httpProcessor : HttpProcessors.client(), connPool, socketConfig != null ? socketConfig : SocketConfig.DEFAULT, connectFactory != null ? connectFactory : new DefaultBHttpClientConnectionFactory( http1Config, CharCodingConfig.DEFAULT), sslSocketFactory, sslSetupHandler != null ? sslSetupHandler : DefaultTlsSetupHandler.CLIENT, sslSessionVerifier, DefaultAddressResolver.INSTANCE); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/ServerBootstrap.java0100644 0000000 0000000 00000045001 00000250600 027517 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.bootstrap; import java.net.InetAddress; import java.util.ArrayList; import java.util.List; import java.util.function.BiFunction; import javax.net.ServerSocketFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLServerSocketFactory; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.ExceptionListener; import org.apache.hc.core5.http.HttpRequestMapper; import org.apache.hc.core5.http.HttpResponseFactory; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.config.NamedElementChain; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.HttpProcessors; import org.apache.hc.core5.http.impl.io.DefaultBHttpServerConnection; import org.apache.hc.core5.http.impl.io.DefaultBHttpServerConnectionFactory; import org.apache.hc.core5.http.impl.io.DefaultClassicHttpResponseFactory; import org.apache.hc.core5.http.impl.io.HttpService; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.io.HttpConnectionFactory; import org.apache.hc.core5.http.io.HttpFilterHandler; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.HttpServerRequestHandler; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.ssl.DefaultTlsSetupHandler; import org.apache.hc.core5.http.io.support.BasicHttpServerExpectationDecorator; import org.apache.hc.core5.http.io.support.BasicHttpServerRequestHandler; import org.apache.hc.core5.http.io.support.HttpServerExpectationFilter; import org.apache.hc.core5.http.io.support.HttpServerFilterChainElement; import org.apache.hc.core5.http.io.support.HttpServerFilterChainRequestHandler; import org.apache.hc.core5.http.io.support.TerminalServerFilter; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http.protocol.UriPatternType; import org.apache.hc.core5.net.InetAddressUtils; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.util.Args; /** * {@link HttpServer} bootstrap. * * @since 4.4 */ @SuppressWarnings("deprecation") public class ServerBootstrap { private final List> routeEntries; private final List> filters; private String canonicalHostName; private HttpRequestMapper requestRouter; private org.apache.hc.core5.http.protocol.LookupRegistry lookupRegistry; private int listenerPort; private InetAddress localAddress; private SocketConfig socketConfig; private Http1Config http1Config; private CharCodingConfig charCodingConfig; private HttpProcessor httpProcessor; private ConnectionReuseStrategy connStrategy; private HttpResponseFactory responseFactory; private ServerSocketFactory serverSocketFactory; private SSLContext sslContext; private Callback sslSetupHandler; private HttpConnectionFactory connectionFactory; private ExceptionListener exceptionListener; private Http1StreamListener streamListener; private BiFunction authorityResolver; private ServerBootstrap() { this.routeEntries = new ArrayList<>(); this.filters = new ArrayList<>(); } public static ServerBootstrap bootstrap() { return new ServerBootstrap(); } /** * Sets canonical name (fully qualified domain name) of the server. * * @since 5.0 */ public final ServerBootstrap setCanonicalHostName(final String canonicalHostName) { this.canonicalHostName = canonicalHostName; return this; } /** * Sets listener port number. */ public final ServerBootstrap setListenerPort(final int listenerPort) { this.listenerPort = listenerPort; return this; } /** * Sets local interface for the listener. */ public final ServerBootstrap setLocalAddress(final InetAddress localAddress) { this.localAddress = localAddress; return this; } /** * Sets socket configuration. */ public final ServerBootstrap setSocketConfig(final SocketConfig socketConfig) { this.socketConfig = socketConfig; return this; } /** * Sets HTTP/1 protocol configuration. */ public final ServerBootstrap setHttp1Config(final Http1Config http1Config) { this.http1Config = http1Config; return this; } /** * Sets connection configuration. */ public final ServerBootstrap setCharCodingConfig(final CharCodingConfig charCodingConfig) { this.charCodingConfig = charCodingConfig; return this; } /** * Sets {@link HttpProcessor} instance. */ public final ServerBootstrap setHttpProcessor(final HttpProcessor httpProcessor) { this.httpProcessor = httpProcessor; return this; } /** * Sets {@link ConnectionReuseStrategy} instance. */ public final ServerBootstrap setConnectionReuseStrategy(final ConnectionReuseStrategy connStrategy) { this.connStrategy = connStrategy; return this; } /** * Sets {@link HttpResponseFactory} instance. */ public final ServerBootstrap setResponseFactory(final HttpResponseFactory responseFactory) { this.responseFactory = responseFactory; return this; } /** * @deprecated Use {@link RequestRouter}. */ @Deprecated public final ServerBootstrap setLookupRegistry(final org.apache.hc.core5.http.protocol.LookupRegistry lookupRegistry) { this.lookupRegistry = lookupRegistry; return this; } /** * Registers the given {@link HttpRequestHandler} as a default handler for URIs * matching the given pattern. * * @param uriPattern the pattern to register the handler for. * @param requestHandler the handler. */ public final ServerBootstrap register(final String uriPattern, final HttpRequestHandler requestHandler) { Args.notBlank(uriPattern, "URI pattern"); Args.notNull(requestHandler, "Supplier"); routeEntries.add(new RequestRouter.Entry<>(uriPattern, requestHandler)); return this; } /** * Registers the given {@link HttpRequestHandler} as a handler for URIs * matching the given host and the pattern. * * @param hostname * @param uriPattern the pattern to register the handler for. * @param requestHandler the handler. * * @since 5.3 */ public final ServerBootstrap register(final String hostname, final String uriPattern, final HttpRequestHandler requestHandler) { Args.notBlank(hostname, "Hostname"); Args.notBlank(uriPattern, "URI pattern"); Args.notNull(requestHandler, "Request handler"); routeEntries.add(new RequestRouter.Entry<>(hostname, uriPattern, requestHandler)); return this; } /** * @deprecated Use {@link #register(String, String, HttpRequestHandler)}. */ @Deprecated public final ServerBootstrap registerVirtual(final String hostname, final String uriPattern, final HttpRequestHandler requestHandler) { return register(hostname, uriPattern, requestHandler); } /** * Sets {@link HttpRequestMapper} instance. * * @see org.apache.hc.core5.http.impl.routing.RequestRouter * @since 5.3 */ public final ServerBootstrap setRequestRouter(final HttpRequestMapper requestRouter) { this.requestRouter = requestRouter; return this; } /** * Sets {@link HttpConnectionFactory} instance. */ public final ServerBootstrap setConnectionFactory( final HttpConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; return this; } /** * Sets {@link javax.net.ServerSocketFactory} instance. */ public final ServerBootstrap setServerSocketFactory(final ServerSocketFactory serverSocketFactory) { this.serverSocketFactory = serverSocketFactory; return this; } /** * Sets {@link javax.net.ssl.SSLContext} instance. *

* Please note this value can be overridden by the {@link #setServerSocketFactory( * javax.net.ServerSocketFactory)} method. */ public final ServerBootstrap setSslContext(final SSLContext sslContext) { this.sslContext = sslContext; return this; } /** * Sets {@link Callback} for {@link SSLParameters}. */ public final ServerBootstrap setSslSetupHandler(final Callback sslSetupHandler) { this.sslSetupHandler = sslSetupHandler; return this; } /** * Sets {@link ExceptionListener} instance. */ public final ServerBootstrap setExceptionListener(final ExceptionListener exceptionListener) { this.exceptionListener = exceptionListener; return this; } /** * Sets {@link ExceptionListener} instance. */ public final ServerBootstrap setStreamListener(final Http1StreamListener streamListener) { this.streamListener = streamListener; return this; } /** * Sets authority resolver to be used when creating the {@link RequestRouter}. * * @return this instance. * @since 5.4 */ public final ServerBootstrap setAuthorityResolver(final BiFunction authorityResolver) { this.authorityResolver = authorityResolver; return this; } /** * Adds the filter before the filter with the given name. */ public final ServerBootstrap addFilterBefore(final String existing, final String name, final HttpFilterHandler filterHandler) { Args.notBlank(existing, "Existing"); Args.notBlank(name, "Name"); Args.notNull(filterHandler, "Filter handler"); filters.add(new FilterEntry<>(FilterEntry.Position.BEFORE, name, filterHandler, existing)); return this; } /** * Adds the filter after the filter with the given name. */ public final ServerBootstrap addFilterAfter(final String existing, final String name, final HttpFilterHandler filterHandler) { Args.notBlank(existing, "Existing"); Args.notBlank(name, "Name"); Args.notNull(filterHandler, "Filter handler"); filters.add(new FilterEntry<>(FilterEntry.Position.AFTER, name, filterHandler, existing)); return this; } /** * Replace an existing filter with the given name with new filter. */ public final ServerBootstrap replaceFilter(final String existing, final HttpFilterHandler filterHandler) { Args.notBlank(existing, "Existing"); Args.notNull(filterHandler, "Filter handler"); filters.add(new FilterEntry<>(FilterEntry.Position.REPLACE, existing, filterHandler, existing)); return this; } /** * Add an filter to the head of the processing list. */ public final ServerBootstrap addFilterFirst(final String name, final HttpFilterHandler filterHandler) { Args.notNull(name, "Name"); Args.notNull(filterHandler, "Filter handler"); filters.add(new FilterEntry<>(FilterEntry.Position.FIRST, name, filterHandler, null)); return this; } /** * Add an filter to the tail of the processing list. */ public final ServerBootstrap addFilterLast(final String name, final HttpFilterHandler filterHandler) { Args.notNull(name, "Name"); Args.notNull(filterHandler, "Filter handler"); filters.add(new FilterEntry<>(FilterEntry.Position.LAST, name, filterHandler, null)); return this; } public HttpServer create() { final String actualCanonicalHostName = canonicalHostName != null ? canonicalHostName : InetAddressUtils.getCanonicalLocalHostName(); final HttpRequestMapper requestRouterCopy; if (lookupRegistry != null && requestRouter == null) { final org.apache.hc.core5.http.protocol.RequestHandlerRegistry handlerRegistry = new org.apache.hc.core5.http.protocol.RequestHandlerRegistry<>( actualCanonicalHostName, () -> lookupRegistry != null ? lookupRegistry : new org.apache.hc.core5.http.protocol.UriPatternMatcher<>()); for (final RequestRouter.Entry entry: routeEntries) { handlerRegistry.register(entry.uriAuthority != null ? entry.uriAuthority.getHostName() : null, entry.route.pattern, entry.route.handler); } requestRouterCopy = handlerRegistry; } else { if (routeEntries.isEmpty()) { requestRouterCopy = requestRouter; } else { requestRouterCopy = RequestRouter.create( new URIAuthority(actualCanonicalHostName), UriPatternType.URI_PATTERN, routeEntries, authorityResolver != null ? authorityResolver : RequestRouter.IGNORE_PORT_AUTHORITY_RESOLVER, requestRouter); } } final HttpServerRequestHandler requestHandler; if (!filters.isEmpty()) { final NamedElementChain filterChainDefinition = new NamedElementChain<>(); filterChainDefinition.addLast( new TerminalServerFilter( requestRouterCopy, this.responseFactory != null ? this.responseFactory : DefaultClassicHttpResponseFactory.INSTANCE), StandardFilter.MAIN_HANDLER.name()); filterChainDefinition.addFirst( new HttpServerExpectationFilter(), StandardFilter.EXPECT_CONTINUE.name()); for (final FilterEntry entry: filters) { switch (entry.position) { case AFTER: filterChainDefinition.addAfter(entry.existing, entry.filterHandler, entry.name); break; case BEFORE: filterChainDefinition.addBefore(entry.existing, entry.filterHandler, entry.name); break; case REPLACE: filterChainDefinition.replace(entry.existing, entry.filterHandler); break; case FIRST: filterChainDefinition.addFirst(entry.filterHandler, entry.name); break; case LAST: // Don't add last, after TerminalServerFilter, as that does not delegate to the chain // Instead, add the filter just before it, making it effectively the last filter filterChainDefinition.addBefore(StandardFilter.MAIN_HANDLER.name(), entry.filterHandler, entry.name); break; } } NamedElementChain.Node current = filterChainDefinition.getLast(); HttpServerFilterChainElement filterChain = null; while (current != null) { filterChain = new HttpServerFilterChainElement(current.getValue(), filterChain); current = current.getPrevious(); } requestHandler = new HttpServerFilterChainRequestHandler(filterChain); } else { requestHandler = new BasicHttpServerExpectationDecorator(new BasicHttpServerRequestHandler( requestRouterCopy, this.responseFactory != null ? this.responseFactory : DefaultClassicHttpResponseFactory.INSTANCE)); } final HttpService httpService = new HttpService( this.httpProcessor != null ? this.httpProcessor : HttpProcessors.server(), requestHandler, this.http1Config, this.connStrategy != null ? this.connStrategy : DefaultConnectionReuseStrategy.INSTANCE, this.streamListener); HttpConnectionFactory connectionFactoryCopy = this.connectionFactory; if (connectionFactoryCopy == null) { final String scheme = serverSocketFactory instanceof SSLServerSocketFactory || sslContext != null ? URIScheme.HTTPS.id : URIScheme.HTTP.id; connectionFactoryCopy = new DefaultBHttpServerConnectionFactory(scheme, this.http1Config, this.charCodingConfig); } return new HttpServer( Math.max(this.listenerPort, 0), httpService, this.localAddress, this.socketConfig != null ? this.socketConfig : SocketConfig.DEFAULT, serverSocketFactory, connectionFactoryCopy, sslContext, sslSetupHandler != null ? sslSetupHandler : DefaultTlsSetupHandler.SERVER, this.exceptionListener != null ? this.exceptionListener : ExceptionListener.NO_OP); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/StandardFilter.java0100644 0000000 0000000 00000002626 00000250600 027267 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.bootstrap; /** * Filters supported by async and classic server implementations * * @see AsyncServerBootstrap * @see ServerBootstrap */ public enum StandardFilter { EXPECT_CONTINUE, MAIN_HANDLER }httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/Worker.java0100644 0000000 0000000 00000004765 00000250600 025640 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.bootstrap; import org.apache.hc.core5.http.ExceptionListener; import org.apache.hc.core5.http.impl.io.HttpService; import org.apache.hc.core5.http.io.HttpServerConnection; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.io.CloseMode; class Worker implements Runnable { private final HttpService httpservice; private final HttpServerConnection conn; private final ExceptionListener exceptionListener; Worker( final HttpService httpservice, final HttpServerConnection conn, final ExceptionListener exceptionListener) { super(); this.httpservice = httpservice; this.conn = conn; this.exceptionListener = exceptionListener; } public HttpServerConnection getConnection() { return this.conn; } @Override public void run() { try { while (!Thread.interrupted() && this.conn.isOpen()) { final HttpCoreContext context = HttpCoreContext.create(); this.httpservice.handleRequest(this.conn, context); } this.conn.close(); } catch (final Exception ex) { this.exceptionListener.onError(this.conn, ex); } finally { this.conn.close(CloseMode.IMMEDIATE); } } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/WorkerPoolExecutor.java0100644 0000000 0000000 00000004743 00000250600 030205 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.bootstrap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; class WorkerPoolExecutor extends ThreadPoolExecutor { private final Map workerSet; public WorkerPoolExecutor( final int corePoolSize, final int maximumPoolSize, final long keepAliveTime, final TimeUnit unit, final BlockingQueue workQueue, final ThreadFactory threadFactory) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); this.workerSet = new ConcurrentHashMap<>(); } @Override protected void beforeExecute(final Thread t, final Runnable r) { if (r instanceof Worker) { this.workerSet.put((Worker) r, Boolean.TRUE); } } @Override protected void afterExecute(final Runnable r, final Throwable t) { if (r instanceof Worker) { this.workerSet.remove(r); } } public Set getWorkers() { return new HashSet<>(this.workerSet.keySet()); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/package-info.java0100644 0000000 0000000 00000002406 00000250600 026701 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * HTTP/1.1 requester and server bootstrap. */ package org.apache.hc.core5.http.impl.bootstrap; httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/0040755 0000000 0000000 00000000000 00000250600 022105 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/AbstractMessageParser.java0100644 0000000 0000000 00000026436 00000250600 027205 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.util.ArrayList; import java.util.List; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpMessage; import org.apache.hc.core5.http.MessageConstraintException; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.io.HttpMessageParser; import org.apache.hc.core5.http.io.SessionInputBuffer; import org.apache.hc.core5.http.message.LazyLineParser; import org.apache.hc.core5.http.message.LineParser; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; /** * Abstract base class for HTTP message parsers that obtain input from * an instance of {@link org.apache.hc.core5.http.io.SessionInputBuffer}. * * @param The type of {@link HttpMessage}. * @since 4.0 */ public abstract class AbstractMessageParser implements HttpMessageParser { private static final int HEAD_LINE = 0; private static final int HEADERS = 1; private final Http1Config http1Config; private final List headerLines; private final CharArrayBuffer headLine; private final LineParser lineParser; private int state; private T message; /** * @since 5.3 */ public AbstractMessageParser(final Http1Config http1Config, final LineParser lineParser) { super(); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.lineParser = lineParser != null ? lineParser : LazyLineParser.INSTANCE; this.headerLines = new ArrayList<>(); this.headLine = new CharArrayBuffer(128); this.state = HEAD_LINE; } /** * @deprecated Use {@link #AbstractMessageParser(Http1Config, LineParser)} */ @Deprecated public AbstractMessageParser(final LineParser lineParser, final Http1Config http1Config) { this(http1Config, lineParser); } LineParser getLineParser() { return this.lineParser; } /** * Parses HTTP headers from the data receiver stream according to the generic * format as specified by the HTTP/1.1 protocol specification. * * @param inBuffer Session input buffer * @param inputStream Input stream * @param maxHeaderCount maximum number of headers allowed. If the number * of headers received from the data stream exceeds maxCount value, an * IOException will be thrown. Setting this parameter to a negative value * or zero will disable the check. * @param maxLineLen maximum number of characters for a header line, * including the continuation lines. Setting this parameter to a negative * value or zero will disable the check. * @return array of HTTP headers * @param lineParser the line parser. If {@code null} * {@link org.apache.hc.core5.http.message.LazyLineParser#INSTANCE} will be used * * @throws IOException in case of an I/O error * @throws HttpException in case of HTTP protocol violation */ public static Header[] parseHeaders( final SessionInputBuffer inBuffer, final InputStream inputStream, final int maxHeaderCount, final int maxLineLen, final LineParser lineParser) throws HttpException, IOException { final List headerLines = new ArrayList<>(); return parseHeaders(inBuffer, inputStream, maxHeaderCount, maxLineLen, lineParser != null ? lineParser : LazyLineParser.INSTANCE, headerLines); } /** * Parses HTTP headers from the data receiver stream according to the generic * format as specified by the HTTP/1.1 protocol specification. * * @param inBuffer Session input buffer * @param inputStream Input stream * @param maxHeaderCount maximum number of headers allowed. If the number * of headers received from the data stream exceeds maxCount value, an * IOException will be thrown. Setting this parameter to a negative value * or zero will disable the check. * @param maxLineLen maximum number of characters for a header line, * including the continuation lines. Setting this parameter to a negative * value or zero will disable the check. * @param parser line parser to use. * @param headerLines List of header lines. This list will be used to store * intermediate results. This makes it possible to resume parsing of * headers in case of a {@link InterruptedIOException}. * * @return array of HTTP headers * * @throws IOException in case of an I/O error * @throws HttpException in case of HTTP protocol violation * * @since 4.1 */ public static Header[] parseHeaders( final SessionInputBuffer inBuffer, final InputStream inputStream, final int maxHeaderCount, final int maxLineLen, final LineParser parser, final List headerLines) throws HttpException, IOException { Args.notNull(inBuffer, "Session input buffer"); Args.notNull(inputStream, "Input stream"); Args.notNull(parser, "Line parser"); Args.notNull(headerLines, "Header line list"); CharArrayBuffer current = null; CharArrayBuffer previous = null; for (;;) { if (current == null) { current = new CharArrayBuffer(64); } else { current.clear(); } final int readLen = inBuffer.readLine(current, inputStream); if (readLen == -1 || current.length() < 1) { break; } // Parse the header name and value // Check for folded headers first // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2 // discussion on folded headers if ((current.charAt(0) == ' ' || current.charAt(0) == '\t') && previous != null) { // we have continuation folded header // so append value int i = 0; while (i < current.length()) { final char ch = current.charAt(i); if (ch != ' ' && ch != '\t') { break; } i++; } if (maxLineLen > 0 && previous.length() + 1 + current.length() - i > maxLineLen) { throw new MessageConstraintException("Maximum line length limit exceeded"); } previous.append(' '); previous.append(current, i, current.length() - i); } else { headerLines.add(current); previous = current; current = null; } if (maxHeaderCount > 0 && headerLines.size() >= maxHeaderCount) { throw new MessageConstraintException("Maximum header count exceeded"); } } final Header[] headers = new Header[headerLines.size()]; for (int i = 0; i < headerLines.size(); i++) { final CharArrayBuffer buffer = headerLines.get(i); headers[i] = parser.parseHeader(buffer); } return headers; } /** * Subclasses must override this method to generate an instance of * {@link HttpMessage} based on the initial input from the session buffer. *

* Usually this method is expected to read just the very first line or * the very first valid from the data stream and based on the input generate * an appropriate instance of {@link HttpMessage}. * * @param buffer the session input buffer. * @return HTTP message based on the input from the session buffer. * @throws IOException in case of an I/O error. * @throws HttpException in case of HTTP protocol violation. * * @since 5.0 */ protected abstract T createMessage(CharArrayBuffer buffer) throws IOException, HttpException; /** * Subclasses must override this method to generate an appropriate exception * in case of unexpected connection termination by the peer endpoint. * * @since 5.0 * * @deprecated do not use. */ @Deprecated protected IOException createConnectionClosedException() { return new ConnectionClosedException(); } @Override public T parse(final SessionInputBuffer buffer, final InputStream inputStream) throws IOException, HttpException { Args.notNull(buffer, "Session input buffer"); Args.notNull(inputStream, "Input stream"); final int st = this.state; switch (st) { case HEAD_LINE: for (int n = 0; n < this.http1Config.getMaxEmptyLineCount(); n++) { this.headLine.clear(); final int i = buffer.readLine(this.headLine, inputStream); if (i == -1) { return null; } if (this.headLine.length() > 0) { this.message = createMessage(this.headLine); if (this.message != null) { break; } } } if (this.message == null) { throw new MessageConstraintException("Maximum empty line limit exceeded"); } this.state = HEADERS; //$FALL-THROUGH$ case HEADERS: final Header[] headers = AbstractMessageParser.parseHeaders( buffer, inputStream, this.http1Config.getMaxHeaderCount(), this.http1Config.getMaxLineLength(), this.lineParser, this.headerLines); this.message.setHeaders(headers); final T result = this.message; this.message = null; this.headerLines.clear(); this.state = HEAD_LINE; return result; default: throw new IllegalStateException("Inconsistent parser state"); } } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/AbstractMessageWriter.java0100644 0000000 0000000 00000010171 00000250600 027212 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.io.OutputStream; import java.util.Iterator; import org.apache.hc.core5.http.FormattedHeader; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpMessage; import org.apache.hc.core5.http.io.HttpMessageWriter; import org.apache.hc.core5.http.io.SessionOutputBuffer; import org.apache.hc.core5.http.message.BasicLineFormatter; import org.apache.hc.core5.http.message.LineFormatter; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; /** * Abstract base class for HTTP message writers that serialize output to * an instance of {@link org.apache.hc.core5.http.io.SessionOutputBuffer}. * * @param The type of {@link HttpMessage}. * @since 4.0 */ public abstract class AbstractMessageWriter implements HttpMessageWriter { private final CharArrayBuffer lineBuf; private final LineFormatter lineFormatter; /** * Creates an instance of AbstractMessageWriter. * * @param formatter the line formatter If {@code null} {@link BasicLineFormatter#INSTANCE} * will be used. * * @since 4.3 */ public AbstractMessageWriter(final LineFormatter formatter) { super(); this.lineFormatter = formatter != null ? formatter : BasicLineFormatter.INSTANCE; this.lineBuf = new CharArrayBuffer(128); } LineFormatter getLineFormatter() { return this.lineFormatter; } /** * Subclasses must override this method to write out the first header line * based on the {@link HttpMessage} passed as a parameter. * * @param message the message whose first line is to be written out. * @param lineBuf line buffer * @throws IOException in case of an I/O error. */ protected abstract void writeHeadLine(T message, CharArrayBuffer lineBuf) throws IOException; @Override public void write(final T message, final SessionOutputBuffer buffer, final OutputStream outputStream) throws IOException, HttpException { Args.notNull(message, "HTTP message"); Args.notNull(buffer, "Session output buffer"); Args.notNull(outputStream, "Output stream"); writeHeadLine(message, this.lineBuf); buffer.writeLine(this.lineBuf, outputStream); for (final Iterator

it = message.headerIterator(); it.hasNext(); ) { final Header header = it.next(); if (header instanceof FormattedHeader) { final CharArrayBuffer chbuffer = ((FormattedHeader) header).getBuffer(); buffer.writeLine(chbuffer, outputStream); } else { this.lineBuf.clear(); lineFormatter.formatHeader(this.lineBuf, header); buffer.writeLine(this.lineBuf, outputStream); } } this.lineBuf.clear(); buffer.writeLine(this.lineBuf, outputStream); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/BHttpConnectionBase.java0100644 0000000 0000000 00000035653 00000250600 026615 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.net.SocketTimeoutException; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.EndpointDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpMessage; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.BasicEndpointDetails; import org.apache.hc.core5.http.impl.BasicHttpConnectionMetrics; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.impl.io.support.IncomingHttpEntity; import org.apache.hc.core5.http.io.BHttpConnection; import org.apache.hc.core5.http.io.SessionInputBuffer; import org.apache.hc.core5.http.io.SessionOutputBuffer; import org.apache.hc.core5.http.io.entity.EmptyInputStream; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.Closer; import org.apache.hc.core5.net.InetAddressUtils; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; class BHttpConnectionBase implements BHttpConnection { private static final Timeout STALE_CHECK_TIMEOUT = Timeout.ofMilliseconds(1); final Http1Config http1Config; final SessionInputBufferImpl inBuffer; final SessionOutputBufferImpl outbuffer; final BasicHttpConnectionMetrics connMetrics; final AtomicReference socketHolderRef; // Lazily initialized chunked request buffer provided to ChunkedOutputStream. private byte[] chunkedRequestBuffer; volatile ProtocolVersion version; volatile EndpointDetails endpointDetails; BHttpConnectionBase( final Http1Config http1Config, final CharsetDecoder charDecoder, final CharsetEncoder charEncoder) { this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; final BasicHttpTransportMetrics inTransportMetrics = new BasicHttpTransportMetrics(); final BasicHttpTransportMetrics outTransportMetrics = new BasicHttpTransportMetrics(); this.inBuffer = new SessionInputBufferImpl(inTransportMetrics, this.http1Config.getBufferSize(), -1, this.http1Config.getMaxLineLength(), charDecoder); this.outbuffer = new SessionOutputBufferImpl(outTransportMetrics, this.http1Config.getBufferSize(), this.http1Config.getChunkSizeHint(), charEncoder); this.connMetrics = new BasicHttpConnectionMetrics(inTransportMetrics, outTransportMetrics); this.socketHolderRef = new AtomicReference<>(); } protected SocketHolder ensureOpen() throws IOException { final SocketHolder socketHolder = this.socketHolderRef.get(); if (socketHolder == null) { throw new ConnectionClosedException(); } return socketHolder; } /** * Binds this connection to the given {@link Socket}. This socket will be * used by the connection to send and receive data. *

* After this method's execution the connection status will be reported * as open and the {@link #isOpen()} will return {@code true}. * * @param socket the socket. * @throws IOException in case of an I/O error. */ protected void bind(final Socket socket) throws IOException { Args.notNull(socket, "Socket"); bind(new SocketHolder(socket)); } protected void bind(final SocketHolder socketHolder) throws IOException { Args.notNull(socketHolder, "Socket holder"); this.socketHolderRef.set(socketHolder); this.endpointDetails = null; } @Override public boolean isOpen() { return this.socketHolderRef.get() != null; } /** * @since 5.0 */ @Override public ProtocolVersion getProtocolVersion() { return this.version; } protected SocketHolder getSocketHolder() { return this.socketHolderRef.get(); } protected OutputStream createContentOutputStream( final long len, final SessionOutputBuffer buffer, final OutputStream outputStream, final Supplier> trailers) { if (len >= 0) { return new ContentLengthOutputStream(buffer, outputStream, len); } else if (len == ContentLengthStrategy.CHUNKED) { return new ChunkedOutputStream(buffer, outputStream, getChunkedRequestBuffer(), trailers); } else { return new IdentityOutputStream(buffer, outputStream); } } private byte[] getChunkedRequestBuffer() { if (chunkedRequestBuffer == null) { final int chunkSizeHint = this.http1Config.getChunkSizeHint(); chunkedRequestBuffer = new byte[chunkSizeHint > 0 ? chunkSizeHint : 8192]; } return chunkedRequestBuffer; } protected InputStream createContentInputStream( final long len, final SessionInputBuffer buffer, final InputStream inputStream) { if (len > 0) { return new ContentLengthInputStream(buffer, inputStream, len); } else if (len == 0) { return EmptyInputStream.INSTANCE; } else if (len == ContentLengthStrategy.CHUNKED) { return new ChunkedInputStream(buffer, inputStream, this.http1Config); } else { return new IdentityInputStream(buffer, inputStream); } } HttpEntity createIncomingEntity( final HttpMessage message, final SessionInputBuffer inBuffer, final InputStream inputStream, final long len) { return new IncomingHttpEntity( createContentInputStream(len, inBuffer, inputStream), len, message); } @Override public SocketAddress getRemoteAddress() { final SocketHolder socketHolder = this.socketHolderRef.get(); return socketHolder != null ? socketHolder.getSocket().getRemoteSocketAddress() : null; } @Override public SocketAddress getLocalAddress() { final SocketHolder socketHolder = this.socketHolderRef.get(); return socketHolder != null ? socketHolder.getSocket().getLocalSocketAddress() : null; } @Override public void setSocketTimeout(final Timeout timeout) { final SocketHolder socketHolder = this.socketHolderRef.get(); if (socketHolder != null) { try { socketHolder.getSocket().setSoTimeout(Timeout.defaultsToInfinite(timeout).toMillisecondsIntBound()); } catch (final SocketException ignore) { // It is not quite clear from the Sun's documentation if there are any // other legitimate cases for a socket exception to be thrown when setting // SO_TIMEOUT besides the socket being already closed } } } @Override public Timeout getSocketTimeout() { final SocketHolder socketHolder = this.socketHolderRef.get(); if (socketHolder != null) { try { return Timeout.ofMilliseconds(socketHolder.getSocket().getSoTimeout()); } catch (final SocketException ignore) { } } return Timeout.INFINITE; } @Override public void close(final CloseMode closeMode) { final SocketHolder socketHolder = this.socketHolderRef.getAndSet(null); if (socketHolder != null) { final SSLSocket sslSocket = socketHolder.getSSLSocket(); final Socket baseSocket = socketHolder.getBaseSocket(); if (closeMode == CloseMode.IMMEDIATE) { try { // force abortive close (RST) baseSocket.setSoLinger(true, 0); } catch (final IOException ignore) { } finally { Closer.closeQuietly(baseSocket); } } else { // Close TLS layer first. try { if (sslSocket != null) { try { try { if (!sslSocket.isOutputShutdown()) { sslSocket.shutdownOutput(); } if (!sslSocket.isInputShutdown()) { sslSocket.shutdownInput(); } } catch (final UnsupportedOperationException ignore) { // Some JREs such as Oracle JDK 1.8 do not support // Socket#shutdownOutput() and Socket#shutdownInput() methods // "because of the TLS half-close policy" } sslSocket.close(); } catch (final IOException ignore) { } } } finally { Closer.closeQuietly(baseSocket); } } } } @Override public void close() throws IOException { final SocketHolder socketHolder = this.socketHolderRef.getAndSet(null); if (socketHolder != null) { try (final Socket baseSocket = socketHolder.getBaseSocket()) { this.inBuffer.clear(); this.outbuffer.flush(socketHolder.getOutputStream()); final SSLSocket sslSocket = socketHolder.getSSLSocket(); if (sslSocket != null) { sslSocket.close(); } } } } private int fillInputBuffer(final Timeout timeout) throws IOException { final SocketHolder socketHolder = ensureOpen(); final Socket socket = socketHolder.getSocket(); final int oldtimeout = socket.getSoTimeout(); try { socket.setSoTimeout(timeout.toMillisecondsIntBound()); return this.inBuffer.fillBuffer(socketHolder.getInputStream()); } finally { socket.setSoTimeout(oldtimeout); } } protected boolean awaitInput(final Timeout timeout) throws IOException { if (this.inBuffer.hasBufferedData()) { return true; } fillInputBuffer(timeout); return this.inBuffer.hasBufferedData(); } @Override public boolean isDataAvailable(final Timeout timeout) throws IOException { ensureOpen(); try { return awaitInput(timeout); } catch (final SocketTimeoutException ex) { return false; } } @Override public boolean isStale() throws IOException { if (!isOpen()) { return true; } try { final int bytesRead = fillInputBuffer(STALE_CHECK_TIMEOUT); return bytesRead < 0; } catch (final SocketTimeoutException ex) { return false; } catch (final SocketException ex) { return true; } } @Override public void flush() throws IOException { final SocketHolder socketHolder = ensureOpen(); this.outbuffer.flush(socketHolder.getOutputStream()); } protected void incrementRequestCount() { this.connMetrics.incrementRequestCount(); } protected void incrementResponseCount() { this.connMetrics.incrementResponseCount(); } @Override public SSLSession getSSLSession() { final SocketHolder socketHolder = this.socketHolderRef.get(); if (socketHolder != null) { final Socket socket = socketHolder.getSocket(); return socket instanceof SSLSocket ? ((SSLSocket) socket).getSession() : null; } return null; } @Override public EndpointDetails getEndpointDetails() { if (endpointDetails == null) { final SocketHolder socketHolder = this.socketHolderRef.get(); if (socketHolder != null) { @SuppressWarnings("resource") final Socket socket = socketHolder.getSocket(); Timeout socketTimeout; try { socketTimeout = Timeout.ofMilliseconds(socket.getSoTimeout()); } catch (final SocketException e) { socketTimeout = Timeout.INFINITE; } endpointDetails = new BasicEndpointDetails( socket.getRemoteSocketAddress(), socket.getLocalSocketAddress(), this.connMetrics, socketTimeout); } } return endpointDetails; } @Override public String toString() { final SocketHolder socketHolder = this.socketHolderRef.get(); if (socketHolder != null) { final Socket socket = socketHolder.getSocket(); final StringBuilder buffer = new StringBuilder(); final SocketAddress remoteAddress = socket.getRemoteSocketAddress(); final SocketAddress localAddress = socket.getLocalSocketAddress(); if (remoteAddress != null && localAddress != null) { InetAddressUtils.formatAddress(buffer, localAddress); buffer.append("<->"); InetAddressUtils.formatAddress(buffer, remoteAddress); } return buffer.toString(); } return "[Not bound]"; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java0100644 0000000 0000000 00000026760 00000250600 026535 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.io.InputStream; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.MalformedChunkCodingException; import org.apache.hc.core5.http.StreamClosedException; import org.apache.hc.core5.http.TruncatedChunkException; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.io.SessionInputBuffer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; /** * Implements chunked transfer coding. The content is received in small chunks. * Entities transferred using this input stream can be of unlimited length. * After the stream is read to the end, it provides access to the trailers, * if any. *

* Note that this class NEVER closes the underlying stream, even when * {@link #close()} gets called. Instead, it will read until the "end" of its * chunking on close, which allows for the seamless execution of subsequent * HTTP 1.1 requests, while not requiring the client to remember to read the * entire contents of the response. * * * @since 4.0 * */ public class ChunkedInputStream extends InputStream { private enum State { CHUNK_LEN, CHUNK_DATA, CHUNK_CRLF, CHUNK_INVALID } private static final int BUFFER_SIZE = 2048; private static final Header[] EMPTY_FOOTERS = {}; /** The session input buffer */ private final SessionInputBuffer buffer; private final InputStream inputStream; private final CharArrayBuffer lineBuffer; private final Http1Config http1Config; private State state; /** The chunk size */ private long chunkSize; /** The current position within the current chunk */ private long pos; /** True if we've reached the end of stream */ private boolean eof; /** True if this stream is closed */ private boolean closed; private Header[] footers = EMPTY_FOOTERS; /** * Default constructor. * * @param buffer Session input buffer * @param inputStream Input stream * @param http1Config Message http1Config. If {@code null} {@link Http1Config#DEFAULT} will be used. * * @since 4.4 */ public ChunkedInputStream(final SessionInputBuffer buffer, final InputStream inputStream, final Http1Config http1Config) { super(); this.buffer = Args.notNull(buffer, "Session input buffer"); this.inputStream = Args.notNull(inputStream, "Input stream"); this.pos = 0L; this.lineBuffer = new CharArrayBuffer(16); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.state = State.CHUNK_LEN; } /** * Wraps session input stream and reads chunk coded input. * * @param buffer Session input buffer * @param inputStream Input stream */ public ChunkedInputStream(final SessionInputBuffer buffer, final InputStream inputStream) { this(buffer, inputStream, null); } @Override public int available() throws IOException { final int len = this.buffer.length(); return (int) Math.min(len, this.chunkSize - this.pos); } /** *

Returns all the data in a chunked stream in coalesced form. A chunk * is followed by a CRLF. The method returns -1 as soon as a chunksize of 0 * is detected.

* *

Trailer headers are read automatically at the end of the stream and * can be obtained with the getResponseFooters() method.

* * @return -1 of the end of the stream has been reached or the next data * byte * @throws IOException in case of an I/O error */ @Override public int read() throws IOException { if (this.closed) { throw new StreamClosedException(); } if (this.eof) { return -1; } if (state != State.CHUNK_DATA) { nextChunk(); if (this.eof) { return -1; } } final int b = buffer.read(inputStream); if (b != -1) { pos++; if (pos >= chunkSize) { state = State.CHUNK_CRLF; } } return b; } /** * Read some bytes from the stream. * @param b The byte array that will hold the contents from the stream. * @param off The offset into the byte array at which bytes will start to be * placed. * @param len the maximum number of bytes that can be returned. * @return The number of bytes returned or -1 if the end of stream has been * reached. * @throws IOException in case of an I/O error */ @Override public int read(final byte[] b, final int off, final int len) throws IOException { if (closed) { throw new StreamClosedException(); } if (len == 0) { return 0; } if (eof) { return -1; } if (state != State.CHUNK_DATA) { nextChunk(); if (eof) { return -1; } } final int bytesRead = buffer.read(b, off, (int) Math.min(len, chunkSize - pos), inputStream); if (bytesRead != -1) { pos += bytesRead; if (pos >= chunkSize) { state = State.CHUNK_CRLF; } return bytesRead; } eof = true; throw new TruncatedChunkException("Truncated chunk (expected size: %d; actual size: %d)", chunkSize, pos); } /** * Read some bytes from the stream. * @param b The byte array that will hold the contents from the stream. * @return The number of bytes returned or -1 if the end of stream has been * reached. * @throws IOException in case of an I/O error */ @Override public int read(final byte[] b) throws IOException { return read(b, 0, b.length); } /** * Read the next chunk. * @throws IOException in case of an I/O error */ private void nextChunk() throws IOException { if (state == State.CHUNK_INVALID) { throw new MalformedChunkCodingException("Corrupt data stream"); } try { chunkSize = getChunkSize(); if (chunkSize < 0L) { throw new MalformedChunkCodingException("Negative chunk size"); } state = State.CHUNK_DATA; pos = 0L; if (chunkSize == 0L) { eof = true; parseTrailerHeaders(); } } catch (final MalformedChunkCodingException ex) { state = State.CHUNK_INVALID; throw ex; } } /** * Expects the stream to start with a chunksize in hex with optional * comments after a semicolon. The line must end with a CRLF: "a3; some * comment\r\n" Positions the stream at the start of the next line. */ private long getChunkSize() throws IOException { final State st = this.state; switch (st) { case CHUNK_CRLF: lineBuffer.clear(); final int bytesRead1 = this.buffer.readLine(lineBuffer, inputStream); if (bytesRead1 == -1) { throw new MalformedChunkCodingException( "CRLF expected at end of chunk"); } if (!lineBuffer.isEmpty()) { throw new MalformedChunkCodingException( "Unexpected content at the end of chunk"); } state = State.CHUNK_LEN; //$FALL-THROUGH$ case CHUNK_LEN: lineBuffer.clear(); final int bytesRead2 = this.buffer.readLine(lineBuffer, inputStream); if (bytesRead2 == -1) { throw new ConnectionClosedException( "Premature end of chunk coded message body: closing chunk expected"); } int separator = lineBuffer.indexOf(';'); if (separator < 0) { separator = lineBuffer.length(); } final String s = this.lineBuffer.substringTrimmed(0, separator); try { return Long.parseLong(s, 16); } catch (final NumberFormatException e) { throw new MalformedChunkCodingException("Bad chunk header: " + s); } default: throw new IllegalStateException("Inconsistent codec state"); } } /** * Reads and stores the Trailer headers. * @throws IOException in case of an I/O error */ private void parseTrailerHeaders() throws IOException { try { this.footers = AbstractMessageParser.parseHeaders(buffer, inputStream, http1Config.getMaxHeaderCount(), http1Config.getMaxLineLength(), null); } catch (final HttpException ex) { final IOException ioe = new MalformedChunkCodingException("Invalid trailing header: " + ex.getMessage()); ioe.initCause(ex); throw ioe; } } /** * Reads the remainder of the chunked message, leaving the underlying * stream at a position to start reading the next response without * scanning. But does NOT close the underlying stream. * @throws IOException in case of an I/O error */ @Override public void close() throws IOException { if (!closed) { try { if (!eof && state != State.CHUNK_INVALID) { // Optimistically check if the content has been fully read // when there's no data remaining in the current chunk. // This is common when self-terminating content (e.g. JSON) // is parsed from response streams. if (chunkSize == pos && chunkSize > 0 && read() == -1) { return; } // read and discard the remainder of the message final byte[] buff = new byte[BUFFER_SIZE]; while (read(buff) >= 0) { } } } finally { eof = true; closed = true; } } } public Header[] getFooters() { return footers.length > 0 ? footers.clone() : EMPTY_FOOTERS; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedOutputStream.java0100644 0000000 0000000 00000021677 00000250600 026740 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.io.OutputStream; import java.util.List; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.FormattedHeader; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.StreamClosedException; import org.apache.hc.core5.http.io.SessionOutputBuffer; import org.apache.hc.core5.http.message.BasicLineFormatter; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; /** * Implements chunked transfer coding. The content is sent in small chunks. * Entities transferred using this output stream can be of unlimited length. * Writes are buffered to an internal buffer (2048 default size). *

* Note that this class NEVER closes the underlying stream, even when * {@link #close()} gets called. Instead, the stream will be marked as * closed and no further output will be permitted. * * * @since 4.0 */ public class ChunkedOutputStream extends OutputStream { private final SessionOutputBuffer buffer; private final OutputStream outputStream; private final byte[] cache; private int cachePosition; private boolean wroteLastChunk; private boolean closed; private final CharArrayBuffer lineBuffer; private final Supplier> trailerSupplier; /** * Default constructor. * * @param buffer Session output buffer * @param outputStream Output stream * @param chunkCache Buffer used to aggregate smaller writes into chunks. * @param trailerSupplier Trailer supplier. May be {@code null} * * @since 5.1 */ public ChunkedOutputStream( final SessionOutputBuffer buffer, final OutputStream outputStream, final byte[] chunkCache, final Supplier> trailerSupplier) { super(); this.buffer = Args.notNull(buffer, "Session output buffer"); this.outputStream = Args.notNull(outputStream, "Output stream"); this.cache = Args.notNull(chunkCache, "Chunk cache"); this.lineBuffer = new CharArrayBuffer(32); this.trailerSupplier = trailerSupplier; } /** * Constructor taking an integer chunk size hint. * * @param buffer Session output buffer * @param outputStream Output stream * @param chunkSizeHint minimal chunk size hint * @param trailerSupplier Trailer supplier. May be {@code null} * * @since 5.0 */ public ChunkedOutputStream( final SessionOutputBuffer buffer, final OutputStream outputStream, final int chunkSizeHint, final Supplier> trailerSupplier) { this(buffer, outputStream, new byte[chunkSizeHint > 0 ? chunkSizeHint : 8192], trailerSupplier); } /** * Constructor with no trailers. * * @param buffer Session output buffer * @param outputStream Output stream * @param chunkSizeHint minimal chunk size hint */ public ChunkedOutputStream(final SessionOutputBuffer buffer, final OutputStream outputStream, final int chunkSizeHint) { this(buffer, outputStream, chunkSizeHint, null); } /** * Writes the cache out onto the underlying stream */ private void flushCache() throws IOException { if (this.cachePosition > 0) { this.lineBuffer.clear(); this.lineBuffer.append(Integer.toHexString(this.cachePosition)); this.buffer.writeLine(this.lineBuffer, this.outputStream); this.buffer.write(this.cache, 0, this.cachePosition, this.outputStream); this.lineBuffer.clear(); this.buffer.writeLine(this.lineBuffer, this.outputStream); this.cachePosition = 0; } } /** * Writes the cache and bufferToAppend to the underlying stream * as one large chunk */ private void flushCacheWithAppend(final byte[] bufferToAppend, final int off, final int len) throws IOException { this.lineBuffer.clear(); this.lineBuffer.append(Integer.toHexString(this.cachePosition + len)); this.buffer.writeLine(this.lineBuffer, this.outputStream); this.buffer.write(this.cache, 0, this.cachePosition, this.outputStream); this.buffer.write(bufferToAppend, off, len, this.outputStream); this.lineBuffer.clear(); this.buffer.writeLine(this.lineBuffer, this.outputStream); this.cachePosition = 0; } private void writeClosingChunk() throws IOException { // Write the final chunk. this.lineBuffer.clear(); this.lineBuffer.append('0'); this.buffer.writeLine(this.lineBuffer, this.outputStream); writeTrailers(); this.lineBuffer.clear(); this.buffer.writeLine(this.lineBuffer, this.outputStream); } private void writeTrailers() throws IOException { final List trailers = this.trailerSupplier != null ? this.trailerSupplier.get() : null; if (trailers != null) { for (int i = 0; i < trailers.size(); i++) { final Header header = trailers.get(i); if (header instanceof FormattedHeader) { final CharArrayBuffer chbuffer = ((FormattedHeader) header).getBuffer(); this.buffer.writeLine(chbuffer, this.outputStream); } else { this.lineBuffer.clear(); BasicLineFormatter.INSTANCE.formatHeader(this.lineBuffer, header); this.buffer.writeLine(this.lineBuffer, this.outputStream); } } } } // ----------------------------------------------------------- Public Methods /** * Must be called to ensure the internal cache is flushed and the closing * chunk is written. * @throws IOException in case of an I/O error */ public void finish() throws IOException { if (!this.wroteLastChunk) { flushCache(); writeClosingChunk(); this.wroteLastChunk = true; } } // -------------------------------------------- OutputStream Methods @Override public void write(final int b) throws IOException { if (this.closed) { throw new StreamClosedException(); } this.cache[this.cachePosition] = (byte) b; this.cachePosition++; if (this.cachePosition == this.cache.length) { flushCache(); } } /** * Writes the array. If the array does not fit within the buffer, it is * not split, but rather written out as one large chunk. */ @Override public void write(final byte[] b) throws IOException { write(b, 0, b.length); } /** * Writes the array. If the array does not fit within the buffer, it is * not split, but rather written out as one large chunk. */ @Override public void write(final byte[] src, final int off, final int len) throws IOException { if (this.closed) { throw new StreamClosedException(); } if (len >= this.cache.length - this.cachePosition) { flushCacheWithAppend(src, off, len); } else { System.arraycopy(src, off, cache, this.cachePosition, len); this.cachePosition += len; } } /** * Flushes the content buffer and the underlying stream. */ @Override public void flush() throws IOException { flushCache(); this.buffer.flush(this.outputStream); } /** * Finishes writing to the underlying stream, but does NOT close the underlying stream. */ @Override public void close() throws IOException { if (!this.closed) { this.closed = true; finish(); this.buffer.flush(this.outputStream); } } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ContentLengthInputStream.java0100644 0000000 0000000 00000017424 00000250600 027725 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.io.InputStream; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.StreamClosedException; import org.apache.hc.core5.http.io.SessionInputBuffer; import org.apache.hc.core5.util.Args; /** * Input stream that cuts off after a defined number of bytes. This class * is used to receive content of HTTP messages where the end of the content * entity is determined by the value of the {@code Content-Length header}. * Entities transferred using this stream can be maximum {@link Long#MAX_VALUE} * long. *

* Note that this class NEVER closes the underlying stream, even when * {@link #close()} gets called. Instead, it will read until the "end" of * its limit on close, which allows for the seamless execution of subsequent * HTTP 1.1 requests, while not requiring the client to remember to read the * entire contents of the response. * * * @since 4.0 */ public class ContentLengthInputStream extends InputStream { private static final int BUFFER_SIZE = 2048; private final SessionInputBuffer buffer; private final InputStream inputStream; /** * The maximum number of bytes that can be read from the stream. Subsequent * read operations will return -1. */ private final long contentLength; /** The current position */ private long pos; /** True if the stream is closed. */ private boolean closed; /** * Default constructor. * * @param buffer Session input buffer * @param inputStream Input stream * @param contentLength The maximum number of bytes that can be read from * the stream. Subsequent read operations will return -1. */ public ContentLengthInputStream(final SessionInputBuffer buffer, final InputStream inputStream, final long contentLength) { super(); this.buffer = Args.notNull(buffer, "Session input buffer"); this.inputStream = Args.notNull(inputStream, "Input stream"); this.contentLength = Args.notNegative(contentLength, "Content length"); } /** *

Reads until the end of the known length of content.

* *

Does NOT close the underlying stream, but instead leaves it * primed to parse the next response.

* @throws IOException If an IO problem occurs. */ @Override public void close() throws IOException { if (!closed) { try { if (pos < contentLength) { final byte[] buffer = new byte[BUFFER_SIZE]; while (read(buffer) >= 0) { // keep reading } } } finally { // close after above so that we don't throw an exception trying // to read after closed! closed = true; } } } @Override public int available() throws IOException { final int len = this.buffer.length(); return Math.min(len, (int) (this.contentLength - this.pos)); } /** * Read the next byte from the stream * @return The next byte or -1 if the end of stream has been reached. * @throws IOException If an IO problem occurs * @see java.io.InputStream#read() */ @Override public int read() throws IOException { if (closed) { throw new StreamClosedException(); } if (pos >= contentLength) { return -1; } final int b = this.buffer.read(this.inputStream); if (b == -1) { if (pos < contentLength) { throw new ConnectionClosedException( "Premature end of Content-Length delimited message body (expected: %d; received: %d)", contentLength, pos); } } else { pos++; } return b; } /** * Does standard {@link InputStream#read(byte[], int, int)} behavior, but * also notifies the watcher when the contents have been consumed. * * @param b The byte array to fill. * @param off Start filling at this position. * @param len The number of bytes to attempt to read. * @return The number of bytes read, or -1 if the end of content has been * reached. * * @throws java.io.IOException Should an error occur on the wrapped stream. */ @Override public int read(final byte[] b, final int off, final int len) throws java.io.IOException { if (closed) { throw new StreamClosedException(); } if (len == 0) { return 0; } if (pos >= contentLength) { return -1; } int chunk = len; if (pos + len > contentLength) { chunk = (int) (contentLength - pos); } final int count = this.buffer.read(b, off, chunk, this.inputStream); if (count == -1 && pos < contentLength) { throw new ConnectionClosedException( "Premature end of Content-Length delimited message body (expected: %d; received: %d)", contentLength, pos); } if (count > 0) { pos += count; } return count; } /** * Read more bytes from the stream. * @param b The byte array to put the new data in. * @return The number of bytes read into the buffer. * @throws IOException If an IO problem occurs * @see java.io.InputStream#read(byte[]) */ @Override public int read(final byte[] b) throws IOException { return read(b, 0, b.length); } /** * Skips and discards a number of bytes from the input stream. * @param n The number of bytes to skip. * @return The actual number of bytes skipped. ≤ 0 if no bytes * are skipped. * @throws IOException If an error occurs while skipping bytes. * @see InputStream#skip(long) */ @Override public long skip(final long n) throws IOException { if (n <= 0) { return 0; } final byte[] buffer = new byte[BUFFER_SIZE]; // make sure we don't skip more bytes than are // still available long remaining = Math.min(n, this.contentLength - this.pos); // skip and keep track of the bytes actually skipped long count = 0; while (remaining > 0) { final int readLen = read(buffer, 0, (int)Math.min(BUFFER_SIZE, remaining)); if (readLen == -1) { break; } count += readLen; remaining -= readLen; } return count; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ContentLengthOutputStream.java0100644 0000000 0000000 00000010622 00000250600 030117 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.io.OutputStream; import org.apache.hc.core5.http.StreamClosedException; import org.apache.hc.core5.http.io.SessionOutputBuffer; import org.apache.hc.core5.util.Args; /** * Output stream that cuts off after a defined number of bytes. This class * is used to send content of HTTP messages where the end of the content entity * is determined by the value of the {@code Content-Length header}. * Entities transferred using this stream can be maximum {@link Long#MAX_VALUE} * long. *

* Note that this class NEVER closes the underlying stream, even when * {@link #close()} gets called. Instead, the stream will be marked as closed * and no further output will be permitted. * * @since 4.0 */ public class ContentLengthOutputStream extends OutputStream { private final SessionOutputBuffer buffer; private final OutputStream outputStream; /** * The maximum number of bytes that can be written the stream. Subsequent * write operations will be ignored. */ private final long contentLength; /** Total bytes written */ private long total; /** True if the stream is closed. */ private boolean closed; /** * Default constructor. * * @param buffer Session output buffer * @param outputStream Output stream * @param contentLength The maximum number of bytes that can be written to * the stream. Subsequent write operations will be ignored. * * @since 4.0 */ public ContentLengthOutputStream(final SessionOutputBuffer buffer, final OutputStream outputStream, final long contentLength) { super(); this.buffer = Args.notNull(buffer, "Session output buffer"); this.outputStream = Args.notNull(outputStream, "Output stream"); this.contentLength = Args.notNegative(contentLength, "Content length"); } /** * Finishes writing to the underlying stream, but does NOT close the underlying stream. * @throws IOException If an I/O problem occurs. */ @Override public void close() throws IOException { if (!this.closed) { this.closed = true; this.buffer.flush(this.outputStream); } } @Override public void flush() throws IOException { this.buffer.flush(this.outputStream); } @Override public void write(final byte[] b, final int off, final int len) throws IOException { if (this.closed) { throw new StreamClosedException(); } if (this.total < this.contentLength) { final long max = this.contentLength - this.total; int chunk = len; if (chunk > max) { chunk = (int) max; } this.buffer.write(b, off, chunk, this.outputStream); this.total += chunk; } } @Override public void write(final byte[] b) throws IOException { write(b, 0, b.length); } @Override public void write(final int b) throws IOException { if (this.closed) { throw new StreamClosedException(); } if (this.total < this.contentLength) { this.buffer.write(b, this.outputStream); this.total++; } } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpClientConnection.java0100644 0000000 0000000 00000037307 00000250600 030464 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.util.Iterator; import javax.net.ssl.SSLSocket; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.LengthRequiredException; import org.apache.hc.core5.http.NoHttpResponseException; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.UnsupportedHttpVersionException; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy; import org.apache.hc.core5.http.io.HttpClientConnection; import org.apache.hc.core5.http.io.HttpMessageParser; import org.apache.hc.core5.http.io.HttpMessageParserFactory; import org.apache.hc.core5.http.io.HttpMessageWriter; import org.apache.hc.core5.http.io.HttpMessageWriterFactory; import org.apache.hc.core5.http.io.ResponseOutOfOrderStrategy; import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.util.Args; /** * Default implementation of {@link HttpClientConnection}. * * @since 4.3 */ public class DefaultBHttpClientConnection extends BHttpConnectionBase implements HttpClientConnection { private final HttpMessageParser responseParser; private final HttpMessageWriter requestWriter; private final ContentLengthStrategy incomingContentStrategy; private final ContentLengthStrategy outgoingContentStrategy; private final ResponseOutOfOrderStrategy responseOutOfOrderStrategy; private volatile boolean consistent; /** * Creates new instance of DefaultBHttpClientConnection. * * @param http1Config Message http1Config. If {@code null} * {@link Http1Config#DEFAULT} will be used. * @param charDecoder decoder to be used for decoding HTTP protocol elements. * If {@code null} simple type cast will be used for byte to char conversion. * @param charEncoder encoder to be used for encoding HTTP protocol elements. * If {@code null} simple type cast will be used for char to byte conversion. * @param incomingContentStrategy incoming content length strategy. If {@code null} * {@link DefaultContentLengthStrategy#INSTANCE} will be used. * @param outgoingContentStrategy outgoing content length strategy. If {@code null} * {@link DefaultContentLengthStrategy#INSTANCE} will be used. * @param responseOutOfOrderStrategy response out of order strategy. If {@code null} * {@link NoResponseOutOfOrderStrategy#INSTANCE} will be used. * @param requestWriterFactory request writer factory. If {@code null} * {@link DefaultHttpRequestWriterFactory#INSTANCE} will be used. * @param responseParserFactory response parser factory. If {@code null} * {@link DefaultHttpResponseParserFactory#INSTANCE} will be used. */ public DefaultBHttpClientConnection( final Http1Config http1Config, final CharsetDecoder charDecoder, final CharsetEncoder charEncoder, final ContentLengthStrategy incomingContentStrategy, final ContentLengthStrategy outgoingContentStrategy, final ResponseOutOfOrderStrategy responseOutOfOrderStrategy, final HttpMessageWriterFactory requestWriterFactory, final HttpMessageParserFactory responseParserFactory) { super(http1Config, charDecoder, charEncoder); this.requestWriter = (requestWriterFactory != null ? requestWriterFactory : DefaultHttpRequestWriterFactory.INSTANCE).create(); this.responseParser = (responseParserFactory != null ? responseParserFactory : DefaultHttpResponseParserFactory.INSTANCE).create(); this.incomingContentStrategy = incomingContentStrategy != null ? incomingContentStrategy : DefaultContentLengthStrategy.INSTANCE; this.outgoingContentStrategy = outgoingContentStrategy != null ? outgoingContentStrategy : DefaultContentLengthStrategy.INSTANCE; this.responseOutOfOrderStrategy = responseOutOfOrderStrategy; this.consistent = true; } /** * Creates new instance of DefaultBHttpClientConnection. * * @param http1Config Message http1Config. If {@code null} * {@link Http1Config#DEFAULT} will be used. * @param charDecoder decoder to be used for decoding HTTP protocol elements. * If {@code null} simple type cast will be used for byte to char conversion. * @param charEncoder encoder to be used for encoding HTTP protocol elements. * If {@code null} simple type cast will be used for char to byte conversion. * @param incomingContentStrategy incoming content length strategy. If {@code null} * {@link DefaultContentLengthStrategy#INSTANCE} will be used. * @param outgoingContentStrategy outgoing content length strategy. If {@code null} * {@link DefaultContentLengthStrategy#INSTANCE} will be used. * @param requestWriterFactory request writer factory. If {@code null} * {@link DefaultHttpRequestWriterFactory#INSTANCE} will be used. * @param responseParserFactory response parser factory. If {@code null} * {@link DefaultHttpResponseParserFactory#INSTANCE} will be used. */ public DefaultBHttpClientConnection( final Http1Config http1Config, final CharsetDecoder charDecoder, final CharsetEncoder charEncoder, final ContentLengthStrategy incomingContentStrategy, final ContentLengthStrategy outgoingContentStrategy, final HttpMessageWriterFactory requestWriterFactory, final HttpMessageParserFactory responseParserFactory) { this( http1Config, charDecoder, charEncoder, incomingContentStrategy, outgoingContentStrategy, null, requestWriterFactory, responseParserFactory); } public DefaultBHttpClientConnection( final Http1Config http1Config, final CharsetDecoder charDecoder, final CharsetEncoder charEncoder) { this(http1Config, charDecoder, charEncoder, null, null, null, null); } public DefaultBHttpClientConnection(final Http1Config http1Config) { this(http1Config, null, null); } protected void onResponseReceived(final ClassicHttpResponse response) { } protected void onRequestSubmitted(final ClassicHttpRequest request) { } @Override public void bind(final Socket socket) throws IOException { super.bind(socket); } /** * @throws IOException in case of an I/O error. * @since 5.3 */ public void bind(final SSLSocket sslSocket, final Socket baseSocket) throws IOException { super.bind(new SocketHolder(sslSocket, baseSocket)); } @Override public void sendRequestHeader(final ClassicHttpRequest request) throws HttpException, IOException { Args.notNull(request, "HTTP request"); final SocketHolder socketHolder = ensureOpen(); this.requestWriter.write(request, this.outbuffer, socketHolder.getOutputStream()); onRequestSubmitted(request); incrementRequestCount(); } @Override public void sendRequestEntity(final ClassicHttpRequest request) throws HttpException, IOException { Args.notNull(request, "HTTP request"); final SocketHolder socketHolder = ensureOpen(); final HttpEntity entity = request.getEntity(); if (entity == null) { return; } final long len = this.outgoingContentStrategy.determineLength(request); if (len == ContentLengthStrategy.UNDEFINED) { throw new LengthRequiredException(); } try (final OutputStream outStream = createContentOutputStream( len, this.outbuffer, new OutputStream() { final OutputStream socketOutputStream = socketHolder.getOutputStream(); final InputStream socketInputStream = socketHolder.getInputStream(); final SSLSocket sslSocket = socketHolder.getSSLSocket(); long totalBytes; void checkTLS(final SSLSocket sslSocket) throws IOException { if (sslSocket.isInputShutdown()) { throw new ConnectionClosedException(); } } void checkForEarlyResponse(final long totalBytesSent, final int nextWriteSize) throws IOException { if (responseOutOfOrderStrategy.isEarlyResponseDetected( request, DefaultBHttpClientConnection.this, socketInputStream, totalBytesSent, nextWriteSize)) { throw new ResponseOutOfOrderException(); } } @Override public void write(final byte[] b) throws IOException { if (sslSocket != null) { checkTLS(sslSocket); } if (responseOutOfOrderStrategy != null) { checkForEarlyResponse(totalBytes, b.length); } totalBytes += b.length; socketOutputStream.write(b); } @Override public void write(final byte[] b, final int off, final int len) throws IOException { if (sslSocket != null) { checkTLS(sslSocket); } if (responseOutOfOrderStrategy != null) { checkForEarlyResponse(totalBytes, len); } totalBytes += len; socketOutputStream.write(b, off, len); } @Override public void write(final int b) throws IOException { if (sslSocket != null) { checkTLS(sslSocket); } if (responseOutOfOrderStrategy != null) { checkForEarlyResponse(totalBytes, 1); } totalBytes++; socketOutputStream.write(b); } @Override public void flush() throws IOException { socketOutputStream.flush(); } @Override public void close() throws IOException { socketOutputStream.close(); } }, entity.getTrailers())) { entity.writeTo(outStream); } catch (final ResponseOutOfOrderException ex) { if (len > 0) { this.consistent = false; } } } @Override public boolean isConsistent() { return this.consistent; } @Override public void terminateRequest(final ClassicHttpRequest request) throws HttpException, IOException { Args.notNull(request, "HTTP request"); final SocketHolder socketHolder = ensureOpen(); final HttpEntity entity = request.getEntity(); if (entity == null) { return; } final Iterator it = MessageSupport.iterateTokens(request, HttpHeaders.CONNECTION); while (it.hasNext()) { final String token = it.next(); if (HeaderElements.CLOSE.equalsIgnoreCase(token)) { this.consistent = false; return; } } final long len = this.outgoingContentStrategy.determineLength(request); if (len == ContentLengthStrategy.CHUNKED) { try (final OutputStream outStream = createContentOutputStream(len, this.outbuffer, socketHolder.getOutputStream(), entity.getTrailers())) { // just close } } else if (len >= 0 && len <= 1024) { try (final OutputStream outStream = createContentOutputStream(len, this.outbuffer, socketHolder.getOutputStream(), null)) { entity.writeTo(outStream); } } else { this.consistent = false; } } @Override public ClassicHttpResponse receiveResponseHeader() throws HttpException, IOException { final SocketHolder socketHolder = ensureOpen(); final ClassicHttpResponse response = this.responseParser.parse(this.inBuffer, socketHolder.getInputStream()); if (response == null) { throw new NoHttpResponseException("The target server failed to respond"); } final ProtocolVersion transportVersion = response.getVersion(); if (transportVersion != null && transportVersion.greaterEquals(HttpVersion.HTTP_2)) { throw new UnsupportedHttpVersionException(transportVersion); } this.version = transportVersion; onResponseReceived(response); final int status = response.getCode(); if (status < HttpStatus.SC_INFORMATIONAL) { throw new ProtocolException("Invalid response: " + status); } if (response.getCode() >= HttpStatus.SC_SUCCESS) { incrementResponseCount(); } return response; } @Override public void receiveResponseEntity( final ClassicHttpResponse response) throws HttpException, IOException { Args.notNull(response, "HTTP response"); final SocketHolder socketHolder = ensureOpen(); final long len = this.incomingContentStrategy.determineLength(response); response.setEntity(createIncomingEntity(response, this.inBuffer, socketHolder.getInputStream(), len)); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpClientConnectionFactory.java0100644 0000000 0000000 00000022047 00000250600 032007 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.net.Socket; import javax.net.ssl.SSLSocket; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.CharCodingSupport; import org.apache.hc.core5.http.io.HttpConnectionFactory; import org.apache.hc.core5.http.io.HttpMessageParserFactory; import org.apache.hc.core5.http.io.HttpMessageWriterFactory; import org.apache.hc.core5.http.io.ResponseOutOfOrderStrategy; /** * Default factory for {@link org.apache.hc.core5.http.io.HttpClientConnection}s. * * @since 4.3 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class DefaultBHttpClientConnectionFactory implements HttpConnectionFactory { private final Http1Config http1Config; private final CharCodingConfig charCodingConfig; private final ContentLengthStrategy incomingContentStrategy; private final ContentLengthStrategy outgoingContentStrategy; private final ResponseOutOfOrderStrategy responseOutOfOrderStrategy; private final HttpMessageWriterFactory requestWriterFactory; private final HttpMessageParserFactory responseParserFactory; private DefaultBHttpClientConnectionFactory( final Http1Config http1Config, final CharCodingConfig charCodingConfig, final ContentLengthStrategy incomingContentStrategy, final ContentLengthStrategy outgoingContentStrategy, final ResponseOutOfOrderStrategy responseOutOfOrderStrategy, final HttpMessageWriterFactory requestWriterFactory, final HttpMessageParserFactory responseParserFactory) { this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.charCodingConfig = charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT; this.incomingContentStrategy = incomingContentStrategy; this.outgoingContentStrategy = outgoingContentStrategy; this.responseOutOfOrderStrategy = responseOutOfOrderStrategy; this.requestWriterFactory = requestWriterFactory != null ? requestWriterFactory : new DefaultHttpRequestWriterFactory(this.http1Config) ; this.responseParserFactory = responseParserFactory != null ? responseParserFactory : new DefaultHttpResponseParserFactory(this.http1Config); } public DefaultBHttpClientConnectionFactory( final Http1Config http1Config, final CharCodingConfig charCodingConfig, final ContentLengthStrategy incomingContentStrategy, final ContentLengthStrategy outgoingContentStrategy, final HttpMessageWriterFactory requestWriterFactory, final HttpMessageParserFactory responseParserFactory) { this( http1Config, charCodingConfig, incomingContentStrategy, outgoingContentStrategy, null, requestWriterFactory, responseParserFactory); } public DefaultBHttpClientConnectionFactory( final Http1Config http1Config, final CharCodingConfig charCodingConfig, final HttpMessageWriterFactory requestWriterFactory, final HttpMessageParserFactory responseParserFactory) { this(http1Config, charCodingConfig, null, null, requestWriterFactory, responseParserFactory); } public DefaultBHttpClientConnectionFactory( final Http1Config http1Config, final CharCodingConfig charCodingConfig) { this(http1Config, charCodingConfig, null, null, null, null); } public DefaultBHttpClientConnectionFactory() { this(null, null, null, null, null, null); } DefaultBHttpClientConnection createDetached() { return new DefaultBHttpClientConnection( this.http1Config, CharCodingSupport.createDecoder(this.charCodingConfig), CharCodingSupport.createEncoder(this.charCodingConfig), this.incomingContentStrategy, this.outgoingContentStrategy, this.responseOutOfOrderStrategy, this.requestWriterFactory, this.responseParserFactory); } @Override public DefaultBHttpClientConnection createConnection(final Socket socket) throws IOException { final DefaultBHttpClientConnection conn = createDetached(); conn.bind(socket); return conn; } /** * @since 5.3 */ @Override public DefaultBHttpClientConnection createConnection(final SSLSocket sslSocket, final Socket socket) throws IOException { final DefaultBHttpClientConnection conn = createDetached(); conn.bind(sslSocket, socket); return conn; } /** * Create a new {@link Builder}. * * @since 5.1 */ public static Builder builder() { return new Builder(); } /** * Builder for {@link DefaultBHttpClientConnectionFactory}. * * @since 5.1 */ public static final class Builder { private Http1Config http1Config; private CharCodingConfig charCodingConfig; private ContentLengthStrategy incomingContentLengthStrategy; private ContentLengthStrategy outgoingContentLengthStrategy; private ResponseOutOfOrderStrategy responseOutOfOrderStrategy; private HttpMessageWriterFactory requestWriterFactory; private HttpMessageParserFactory responseParserFactory; private Builder() { } public Builder http1Config(final Http1Config http1Config) { this.http1Config = http1Config; return this; } public Builder charCodingConfig(final CharCodingConfig charCodingConfig) { this.charCodingConfig = charCodingConfig; return this; } public Builder incomingContentLengthStrategy(final ContentLengthStrategy incomingContentLengthStrategy) { this.incomingContentLengthStrategy = incomingContentLengthStrategy; return this; } public Builder outgoingContentLengthStrategy(final ContentLengthStrategy outgoingContentLengthStrategy) { this.outgoingContentLengthStrategy = outgoingContentLengthStrategy; return this; } public Builder responseOutOfOrderStrategy(final ResponseOutOfOrderStrategy responseOutOfOrderStrategy) { this.responseOutOfOrderStrategy = responseOutOfOrderStrategy; return this; } public Builder requestWriterFactory( final HttpMessageWriterFactory requestWriterFactory) { this.requestWriterFactory = requestWriterFactory; return this; } public Builder responseParserFactory( final HttpMessageParserFactory responseParserFactory) { this.responseParserFactory = responseParserFactory; return this; } public DefaultBHttpClientConnectionFactory build() { return new DefaultBHttpClientConnectionFactory( http1Config, charCodingConfig, incomingContentLengthStrategy, outgoingContentLengthStrategy, responseOutOfOrderStrategy, requestWriterFactory, responseParserFactory); } } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpServerConnection.java0100644 0000000 0000000 00000021000 00000250600 030473 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import javax.net.ssl.SSLSocket; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.UnsupportedHttpVersionException; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy; import org.apache.hc.core5.http.io.HttpMessageParser; import org.apache.hc.core5.http.io.HttpMessageParserFactory; import org.apache.hc.core5.http.io.HttpMessageWriter; import org.apache.hc.core5.http.io.HttpMessageWriterFactory; import org.apache.hc.core5.http.io.HttpServerConnection; import org.apache.hc.core5.util.Args; /** * Default implementation of {@link HttpServerConnection}. * * @since 4.3 */ public class DefaultBHttpServerConnection extends BHttpConnectionBase implements HttpServerConnection { private final String scheme; private final ContentLengthStrategy incomingContentStrategy; private final ContentLengthStrategy outgoingContentStrategy; private final HttpMessageParser requestParser; private final HttpMessageWriter responseWriter; /** * Creates new instance of DefaultBHttpServerConnection. * * @param scheme protocol scheme * @param http1Config Message http1Config. If {@code null} * {@link Http1Config#DEFAULT} will be used. * @param charDecoder decoder to be used for decoding HTTP protocol elements. * If {@code null} simple type cast will be used for byte to char conversion. * @param charEncoder encoder to be used for encoding HTTP protocol elements. * If {@code null} simple type cast will be used for char to byte conversion. * @param incomingContentStrategy incoming content length strategy. If {@code null} * {@link DefaultContentLengthStrategy#INSTANCE} will be used. * @param outgoingContentStrategy outgoing content length strategy. If {@code null} * {@link DefaultContentLengthStrategy#INSTANCE} will be used. * @param requestParserFactory request parser factory. If {@code null} * {@link DefaultHttpRequestParserFactory#INSTANCE} will be used. * @param responseWriterFactory response writer factory. If {@code null} * {@link DefaultHttpResponseWriterFactory#INSTANCE} will be used. */ public DefaultBHttpServerConnection( final String scheme, final Http1Config http1Config, final CharsetDecoder charDecoder, final CharsetEncoder charEncoder, final ContentLengthStrategy incomingContentStrategy, final ContentLengthStrategy outgoingContentStrategy, final HttpMessageParserFactory requestParserFactory, final HttpMessageWriterFactory responseWriterFactory) { super(http1Config, charDecoder, charEncoder); this.scheme = scheme != null ? scheme : URIScheme.HTTP.getId(); this.requestParser = (requestParserFactory != null ? requestParserFactory : DefaultHttpRequestParserFactory.INSTANCE).create(); this.responseWriter = (responseWriterFactory != null ? responseWriterFactory : DefaultHttpResponseWriterFactory.INSTANCE).create(); this.incomingContentStrategy = incomingContentStrategy != null ? incomingContentStrategy : DefaultContentLengthStrategy.INSTANCE; this.outgoingContentStrategy = outgoingContentStrategy != null ? outgoingContentStrategy : DefaultContentLengthStrategy.INSTANCE; } public DefaultBHttpServerConnection( final String scheme, final Http1Config http1Config, final CharsetDecoder charDecoder, final CharsetEncoder charEncoder) { this(scheme, http1Config, charDecoder, charEncoder, null, null, null, null); } public DefaultBHttpServerConnection( final String scheme, final Http1Config http1Config) { this(scheme, http1Config, null, null); } protected void onRequestReceived(final ClassicHttpRequest request) { } protected void onResponseSubmitted(final ClassicHttpResponse response) { } @Override public void bind(final Socket socket) throws IOException { super.bind(socket); } /** * @throws IOException in case of an I/O error. * @since 5.3 */ public void bind(final SSLSocket sslSocket, final Socket baseSocket) throws IOException { super.bind(new SocketHolder(sslSocket, baseSocket)); } @Override public ClassicHttpRequest receiveRequestHeader() throws HttpException, IOException { final SocketHolder socketHolder = ensureOpen(); final ClassicHttpRequest request = this.requestParser.parse(this.inBuffer, socketHolder.getInputStream()); if (request == null) { return null; } final ProtocolVersion transportVersion = request.getVersion(); if (transportVersion != null && transportVersion.greaterEquals(HttpVersion.HTTP_2)) { throw new UnsupportedHttpVersionException(transportVersion); } request.setScheme(this.scheme); this.version = transportVersion; onRequestReceived(request); incrementRequestCount(); return request; } @Override public void receiveRequestEntity(final ClassicHttpRequest request) throws HttpException, IOException { Args.notNull(request, "HTTP request"); final SocketHolder socketHolder = ensureOpen(); final long len = this.incomingContentStrategy.determineLength(request); if (len == ContentLengthStrategy.UNDEFINED) { return; } request.setEntity(createIncomingEntity(request, this.inBuffer, socketHolder.getInputStream(), len)); } @Override public void sendResponseHeader(final ClassicHttpResponse response) throws HttpException, IOException { Args.notNull(response, "HTTP response"); final SocketHolder socketHolder = ensureOpen(); this.responseWriter.write(response, this.outbuffer, socketHolder.getOutputStream()); onResponseSubmitted(response); if (response.getCode() >= HttpStatus.SC_SUCCESS) { incrementResponseCount(); } } @Override public void sendResponseEntity(final ClassicHttpResponse response) throws HttpException, IOException { Args.notNull(response, "HTTP response"); final SocketHolder socketHolder = ensureOpen(); final HttpEntity entity = response.getEntity(); if (entity == null) { return; } final long len = this.outgoingContentStrategy.determineLength(response); try (final OutputStream outStream = createContentOutputStream(len, this.outbuffer, socketHolder.getOutputStream(), entity.getTrailers())) { entity.writeTo(outStream); } } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpServerConnectionFactory.java0100644 0000000 0000000 00000020171 00000250600 032033 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.net.Socket; import javax.net.ssl.SSLSocket; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.CharCodingSupport; import org.apache.hc.core5.http.io.HttpConnectionFactory; import org.apache.hc.core5.http.io.HttpMessageParserFactory; import org.apache.hc.core5.http.io.HttpMessageWriterFactory; /** * Default factory for {@link org.apache.hc.core5.http.io.HttpServerConnection}s. * * @since 4.3 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class DefaultBHttpServerConnectionFactory implements HttpConnectionFactory { private final String scheme; private final Http1Config http1Config; private final CharCodingConfig charCodingConfig; private final ContentLengthStrategy incomingContentStrategy; private final ContentLengthStrategy outgoingContentStrategy; private final HttpMessageParserFactory requestParserFactory; private final HttpMessageWriterFactory responseWriterFactory; public DefaultBHttpServerConnectionFactory( final String scheme, final Http1Config http1Config, final CharCodingConfig charCodingConfig, final ContentLengthStrategy incomingContentStrategy, final ContentLengthStrategy outgoingContentStrategy, final HttpMessageParserFactory requestParserFactory, final HttpMessageWriterFactory responseWriterFactory) { super(); this.scheme = scheme; this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.charCodingConfig = charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT; this.incomingContentStrategy = incomingContentStrategy; this.outgoingContentStrategy = outgoingContentStrategy; this.requestParserFactory = requestParserFactory != null ? requestParserFactory : new DefaultHttpRequestParserFactory(this.http1Config); this.responseWriterFactory = responseWriterFactory != null ? responseWriterFactory : new DefaultHttpResponseWriterFactory(this.http1Config); } public DefaultBHttpServerConnectionFactory( final String scheme, final Http1Config http1Config, final CharCodingConfig charCodingConfig, final HttpMessageParserFactory requestParserFactory, final HttpMessageWriterFactory responseWriterFactory) { this(scheme, http1Config, charCodingConfig, null, null, requestParserFactory, responseWriterFactory); } public DefaultBHttpServerConnectionFactory( final String scheme, final Http1Config http1Config, final CharCodingConfig charCodingConfig) { this(scheme, http1Config, charCodingConfig, null, null, null, null); } DefaultBHttpServerConnection createDetached(final Socket socket) { return new DefaultBHttpServerConnection( scheme != null ? scheme : (socket instanceof SSLSocket ? URIScheme.HTTPS.id : URIScheme.HTTP.id), this.http1Config, CharCodingSupport.createDecoder(this.charCodingConfig), CharCodingSupport.createEncoder(this.charCodingConfig), this.incomingContentStrategy, this.outgoingContentStrategy, this.requestParserFactory, this.responseWriterFactory); } @Override public DefaultBHttpServerConnection createConnection(final Socket socket) throws IOException { final DefaultBHttpServerConnection conn = createDetached(socket); conn.bind(socket); return conn; } /** * @since 5.3 */ @Override public DefaultBHttpServerConnection createConnection(final SSLSocket sslSocket, final Socket socket) throws IOException { final DefaultBHttpServerConnection conn = createDetached(sslSocket); conn.bind(sslSocket, socket); return conn; } /** * Create a new {@link Builder}. * * @since 5.1 */ public static Builder builder() { return new Builder(); } /** * Builder for {@link DefaultBHttpServerConnectionFactory}. * * @since 5.1 */ public static final class Builder { private String scheme; private Http1Config http1Config; private CharCodingConfig charCodingConfig; private ContentLengthStrategy incomingContentLengthStrategy; private ContentLengthStrategy outgoingContentLengthStrategy; private HttpMessageParserFactory requestParserFactory; private HttpMessageWriterFactory responseWriterFactory; private Builder() { } public Builder scheme(final String scheme) { this.scheme = scheme; return this; } public Builder http1Config(final Http1Config http1Config) { this.http1Config = http1Config; return this; } public Builder charCodingConfig(final CharCodingConfig charCodingConfig) { this.charCodingConfig = charCodingConfig; return this; } public Builder incomingContentLengthStrategy(final ContentLengthStrategy incomingContentLengthStrategy) { this.incomingContentLengthStrategy = incomingContentLengthStrategy; return this; } public Builder outgoingContentLengthStrategy(final ContentLengthStrategy outgoingContentLengthStrategy) { this.outgoingContentLengthStrategy = outgoingContentLengthStrategy; return this; } public Builder requestParserFactory( final HttpMessageParserFactory requestParserFactory) { this.requestParserFactory = requestParserFactory; return this; } public Builder responseWriterFactory( final HttpMessageWriterFactory responseWriterFactory) { this.responseWriterFactory = responseWriterFactory; return this; } public DefaultBHttpServerConnectionFactory build() { return new DefaultBHttpServerConnectionFactory( scheme, http1Config, charCodingConfig, incomingContentLengthStrategy, outgoingContentLengthStrategy, requestParserFactory, responseWriterFactory); } } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultClassicHttpRequestFactory.java0100644 0000000 0000000 00000004234 00000250600 031377 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.net.URI; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.HttpRequestFactory; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; /** * Default factory for creating {@link ClassicHttpRequest} objects. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class DefaultClassicHttpRequestFactory implements HttpRequestFactory { public static final DefaultClassicHttpRequestFactory INSTANCE = new DefaultClassicHttpRequestFactory(); @Override public ClassicHttpRequest newHttpRequest(final String method, final URI uri) { return new BasicClassicHttpRequest(method, uri); } @Override public ClassicHttpRequest newHttpRequest(final String method, final String uri) { return new BasicClassicHttpRequest(method, uri); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultClassicHttpResponseFactory.java0100644 0000000 0000000 00000005663 00000250600 031554 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpResponseFactory; import org.apache.hc.core5.http.ReasonPhraseCatalog; import org.apache.hc.core5.http.impl.EnglishReasonPhraseCatalog; import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.util.Args; /** * Default factory for creating {@link ClassicHttpResponse} objects. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class DefaultClassicHttpResponseFactory implements HttpResponseFactory { public static final DefaultClassicHttpResponseFactory INSTANCE = new DefaultClassicHttpResponseFactory(); private final ReasonPhraseCatalog reasonCatalog; /** * Creates a new response factory with the given catalog. * * @param catalog the catalog of reason phrases */ public DefaultClassicHttpResponseFactory(final ReasonPhraseCatalog catalog) { this.reasonCatalog = Args.notNull(catalog, "Reason phrase catalog"); } /** * Creates a new response factory with the default catalog. * The default catalog is {@link EnglishReasonPhraseCatalog}. */ public DefaultClassicHttpResponseFactory() { this(EnglishReasonPhraseCatalog.INSTANCE); } @Override public ClassicHttpResponse newHttpResponse(final int status, final String reasonPhrase) { return new BasicClassicHttpResponse(status, reasonPhrase); } @Override public ClassicHttpResponse newHttpResponse(final int status) { return new BasicClassicHttpResponse(status, this.reasonCatalog, null); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestParser.java0100644 0000000 0000000 00000007661 00000250600 027731 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.io.InputStream; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequestFactory; import org.apache.hc.core5.http.MessageConstraintException; import org.apache.hc.core5.http.RequestHeaderFieldsTooLargeException; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.io.SessionInputBuffer; import org.apache.hc.core5.http.message.LineParser; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.util.CharArrayBuffer; /** * HTTP request parser that obtain its input from an instance * of {@link org.apache.hc.core5.http.io.SessionInputBuffer}. * * @since 4.2 */ public class DefaultHttpRequestParser extends AbstractMessageParser { private final HttpRequestFactory requestFactory; /** * @since 5.3 */ public DefaultHttpRequestParser( final Http1Config http1Config, final LineParser lineParser, final HttpRequestFactory requestFactory) { super(http1Config, lineParser); this.requestFactory = requestFactory != null ? requestFactory : DefaultClassicHttpRequestFactory.INSTANCE; } /** * @deprecated Use {@link #DefaultHttpRequestParser(Http1Config, LineParser, HttpRequestFactory)} */ @Deprecated public DefaultHttpRequestParser( final LineParser lineParser, final HttpRequestFactory requestFactory, final Http1Config http1Config) { this(http1Config, lineParser, requestFactory); } /** * @since 4.3 */ public DefaultHttpRequestParser(final Http1Config http1Config) { this(http1Config, null, null); } /** * @since 4.3 */ public DefaultHttpRequestParser() { this(Http1Config.DEFAULT, null, null); } @Override public ClassicHttpRequest parse( final SessionInputBuffer buffer, final InputStream inputStream) throws IOException, HttpException { try { return super.parse(buffer, inputStream); } catch (final MessageConstraintException ex) { throw new RequestHeaderFieldsTooLargeException(ex.getMessage(), ex); } } @Override protected ClassicHttpRequest createMessage(final CharArrayBuffer buffer) throws IOException, HttpException { final RequestLine requestLine = getLineParser().parseRequestLine(buffer); final ClassicHttpRequest request = this.requestFactory.newHttpRequest(requestLine.getMethod(), requestLine.getUri()); request.setVersion(requestLine.getProtocolVersion()); return request; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestParserFactory.java0100644 0000000 0000000 00000007120 00000250600 031247 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.HttpRequestFactory; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.io.HttpMessageParser; import org.apache.hc.core5.http.io.HttpMessageParserFactory; import org.apache.hc.core5.http.message.LazyLineParser; import org.apache.hc.core5.http.message.LineParser; /** * Default factory for request message parsers. * * @since 4.3 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class DefaultHttpRequestParserFactory implements HttpMessageParserFactory { public static final DefaultHttpRequestParserFactory INSTANCE = new DefaultHttpRequestParserFactory(); private final Http1Config http1Config; private final LineParser lineParser; private final HttpRequestFactory requestFactory; /** * @since 5.3 */ public DefaultHttpRequestParserFactory( final Http1Config http1Config, final LineParser lineParser, final HttpRequestFactory requestFactory) { super(); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.lineParser = lineParser != null ? lineParser : LazyLineParser.INSTANCE; this.requestFactory = requestFactory != null ? requestFactory : DefaultClassicHttpRequestFactory.INSTANCE; } public DefaultHttpRequestParserFactory(final LineParser lineParser, final HttpRequestFactory requestFactory) { this(null, lineParser, requestFactory); } /** * @since 5.3 */ public DefaultHttpRequestParserFactory(final Http1Config http1Config) { this(http1Config, null, null); } public DefaultHttpRequestParserFactory() { this(null, null); } /** * @deprecated Use {@link #create()} */ @Deprecated @Override public HttpMessageParser create(final Http1Config http1Config) { return new DefaultHttpRequestParser(http1Config, this.lineParser, this.requestFactory); } @Override public HttpMessageParser create() { return new DefaultHttpRequestParser(this.http1Config, this.lineParser, this.requestFactory); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestWriter.java0100644 0000000 0000000 00000005612 00000250600 027743 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.message.LineFormatter; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.util.CharArrayBuffer; /** * HTTP request writer that serializes its output to an instance of * {@link org.apache.hc.core5.http.io.SessionOutputBuffer}. * * @since 4.3 */ public class DefaultHttpRequestWriter extends AbstractMessageWriter { private final Http1Config http1Config; /** * @since 5.3 */ public DefaultHttpRequestWriter(final Http1Config http1Config, final LineFormatter formatter) { super(formatter); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; } public DefaultHttpRequestWriter(final LineFormatter formatter) { this(null, formatter); } public DefaultHttpRequestWriter() { this(null, null); } /** * Determines the HTTP protocol version to be communicated to the opposite * endpoint in the message header. * * @since 5.3 */ protected HttpVersion protocolVersion(final HttpRequest message) { return http1Config.getVersion(); } @Override protected void writeHeadLine( final ClassicHttpRequest message, final CharArrayBuffer lineBuf) throws IOException { getLineFormatter().formatRequestLine(lineBuf, new RequestLine( message.getMethod(), message.getRequestUri(), protocolVersion(message))); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestWriterFactory.java0100644 0000000 0000000 00000005625 00000250600 031277 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.io.HttpMessageWriter; import org.apache.hc.core5.http.io.HttpMessageWriterFactory; import org.apache.hc.core5.http.message.BasicLineFormatter; import org.apache.hc.core5.http.message.LineFormatter; /** * Default factory for request message writers. * * @since 4.3 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class DefaultHttpRequestWriterFactory implements HttpMessageWriterFactory { public static final DefaultHttpRequestWriterFactory INSTANCE = new DefaultHttpRequestWriterFactory(); private final Http1Config http1Config; private final LineFormatter lineFormatter; /** * @since 5.3 */ public DefaultHttpRequestWriterFactory(final Http1Config http1Config, final LineFormatter lineFormatter) { super(); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.lineFormatter = lineFormatter != null ? lineFormatter : BasicLineFormatter.INSTANCE; } /** * @since 5.3 */ public DefaultHttpRequestWriterFactory(final Http1Config http1Config) { this(http1Config, null); } public DefaultHttpRequestWriterFactory(final LineFormatter lineFormatter) { this(null, lineFormatter); } public DefaultHttpRequestWriterFactory() { this(null, null); } @Override public HttpMessageWriter create() { return new DefaultHttpRequestWriter(http1Config, lineFormatter); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseParser.java0100644 0000000 0000000 00000006566 00000250600 030102 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpResponseFactory; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.message.LineParser; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.util.CharArrayBuffer; /** * HTTP response parser that obtain its input from an instance * of {@link org.apache.hc.core5.http.io.SessionInputBuffer}. * * @since 4.2 */ public class DefaultHttpResponseParser extends AbstractMessageParser { private final HttpResponseFactory responseFactory; /** * @since 5.3 */ public DefaultHttpResponseParser( final Http1Config http1Config, final LineParser lineParser, final HttpResponseFactory responseFactory) { super(http1Config, lineParser); this.responseFactory = responseFactory != null ? responseFactory : DefaultClassicHttpResponseFactory.INSTANCE; } /** * @deprecated Use {@link #DefaultHttpResponseParser(Http1Config, LineParser, HttpResponseFactory)} */ @Deprecated public DefaultHttpResponseParser( final LineParser lineParser, final HttpResponseFactory responseFactory, final Http1Config http1Config) { this(http1Config, lineParser, responseFactory); } /** * @since 4.3 */ public DefaultHttpResponseParser(final Http1Config http1Config) { this(http1Config, null, null); } /** * @since 4.3 */ public DefaultHttpResponseParser() { this(Http1Config.DEFAULT); } @Override protected ClassicHttpResponse createMessage(final CharArrayBuffer buffer) throws IOException, HttpException { final StatusLine statusline = getLineParser().parseStatusLine(buffer); final ClassicHttpResponse response = this.responseFactory.newHttpResponse(statusline.getStatusCode(), statusline.getReasonPhrase()); response.setVersion(statusline.getProtocolVersion()); return response; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseParserFactory.java0100644 0000000 0000000 00000007165 00000250600 031426 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpResponseFactory; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.io.HttpMessageParser; import org.apache.hc.core5.http.io.HttpMessageParserFactory; import org.apache.hc.core5.http.message.LazyLaxLineParser; import org.apache.hc.core5.http.message.LineParser; /** * Default factory for response message parsers. * * @since 4.3 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class DefaultHttpResponseParserFactory implements HttpMessageParserFactory { public static final DefaultHttpResponseParserFactory INSTANCE = new DefaultHttpResponseParserFactory(); private final Http1Config http1Config; private final LineParser lineParser; private final HttpResponseFactory responseFactory; /** * @since 5.3 */ public DefaultHttpResponseParserFactory( final Http1Config http1Config, final LineParser lineParser, final HttpResponseFactory responseFactory) { super(); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.lineParser = lineParser != null ? lineParser : LazyLaxLineParser.INSTANCE; this.responseFactory = responseFactory != null ? responseFactory : DefaultClassicHttpResponseFactory.INSTANCE; } public DefaultHttpResponseParserFactory(final LineParser lineParser, final HttpResponseFactory responseFactory) { this(null, lineParser, responseFactory); } /** * @since 5.3 */ public DefaultHttpResponseParserFactory(final Http1Config http1Config) { this(http1Config, null, null); } public DefaultHttpResponseParserFactory() { this(null, null); } /** * @deprecated Use {@link #create()} */ @Deprecated @Override public HttpMessageParser create(final Http1Config http1Config) { return new DefaultHttpResponseParser(http1Config, this.lineParser, this.responseFactory); } @Override public HttpMessageParser create() { return new DefaultHttpResponseParser(this.http1Config, this.lineParser, this.responseFactory); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseWriter.java0100644 0000000 0000000 00000005621 00000250600 030111 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.message.LineFormatter; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.util.CharArrayBuffer; /** * HTTP response writer that serializes its output to an instance of * {@link org.apache.hc.core5.http.io.SessionOutputBuffer}. * * @since 4.3 */ public class DefaultHttpResponseWriter extends AbstractMessageWriter { private final Http1Config http1Config; /** * @since 5.3 */ public DefaultHttpResponseWriter(final Http1Config http1Config, final LineFormatter formatter) { super(formatter); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; } public DefaultHttpResponseWriter(final LineFormatter formatter) { this(null, formatter); } public DefaultHttpResponseWriter() { this(null, null); } /** * Determines the HTTP protocol version to be communicated to the opposite * endpoint in the message header. * * @since 5.3 */ protected HttpVersion protocolVersion(final HttpResponse message) { return http1Config.getVersion(); } @Override protected void writeHeadLine( final ClassicHttpResponse message, final CharArrayBuffer lineBuf) throws IOException { getLineFormatter().formatStatusLine(lineBuf, new StatusLine( protocolVersion(message), message.getCode(), message.getReasonPhrase())); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseWriterFactory.java0100644 0000000 0000000 00000005641 00000250600 031443 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.io.HttpMessageWriter; import org.apache.hc.core5.http.io.HttpMessageWriterFactory; import org.apache.hc.core5.http.message.BasicLineFormatter; import org.apache.hc.core5.http.message.LineFormatter; /** * Default factory for response message writers. * * @since 4.3 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class DefaultHttpResponseWriterFactory implements HttpMessageWriterFactory { public static final DefaultHttpResponseWriterFactory INSTANCE = new DefaultHttpResponseWriterFactory(); private final Http1Config http1Config; private final LineFormatter lineFormatter; /** * @since 5.3 */ public DefaultHttpResponseWriterFactory(final Http1Config http1Config, final LineFormatter lineFormatter) { super(); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.lineFormatter = lineFormatter != null ? lineFormatter : BasicLineFormatter.INSTANCE; } /** * @since 5.3 */ public DefaultHttpResponseWriterFactory(final Http1Config http1Config) { this(http1Config, null); } public DefaultHttpResponseWriterFactory(final LineFormatter lineFormatter) { this(null, lineFormatter); } public DefaultHttpResponseWriterFactory() { this(null, null); } @Override public HttpMessageWriter create() { return new DefaultHttpResponseWriter(http1Config, lineFormatter); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/EmptyInputStream.java0100644 0000000 0000000 00000005262 00000250600 026244 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.InputStream; /** * @since 4.4 * @deprecated Please use {@link org.apache.hc.core5.http.io.entity.EmptyInputStream} */ @Deprecated public final class EmptyInputStream extends InputStream { public static final EmptyInputStream INSTANCE = new EmptyInputStream(); private EmptyInputStream() { // noop. } /** * Returns {@code 0}. */ @Override public int available() { return 0; } /** * Noop. */ @Override public void close() { // noop. } /** * Noop. */ @SuppressWarnings("sync-override") @Override public void mark(final int readLimit) { // noop. } /** * Returns {@code true}. */ @Override public boolean markSupported() { return true; } /** * Returns {@code -1}. */ @Override public int read() { return -1; } /** * Returns {@code -1}. */ @Override public int read(final byte[] buf) { return -1; } /** * Returns {@code -1}. */ @Override public int read(final byte[] buf, final int off, final int len) { if (len == 0) { return 0; } return -1; } /** * Noop. */ @SuppressWarnings("sync-override") @Override public void reset() { // noop } /** * Returns {@code 0}. */ @Override public long skip(final long n) { return 0L; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpRequestExecutor.java0100644 0000000 0000000 00000040072 00000250600 026757 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.net.SocketTimeoutException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.UnsupportedHttpVersionException; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.io.HttpClientConnection; import org.apache.hc.core5.http.io.HttpResponseInformationCallback; import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.Closer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; import javax.net.ssl.SSLException; /** * {@code HttpRequestExecutor} is a client side HTTP protocol handler based * on the blocking (classic) I/O model. *

* {@code HttpRequestExecutor} relies on {@link HttpProcessor} to generate * mandatory protocol headers for all outgoing messages and apply common, * cross-cutting message transformations to all incoming and outgoing messages. * Application specific processing can be implemented outside * {@code HttpRequestExecutor} once the request has been executed and * a response has been received. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class HttpRequestExecutor { public static final Timeout DEFAULT_WAIT_FOR_CONTINUE = Timeout.ofSeconds(3); private final Http1Config http1Config; private final ConnectionReuseStrategy connReuseStrategy; private final Http1StreamListener streamListener; /** * Creates new instance of HttpRequestExecutor. * * @since 5.3 */ public HttpRequestExecutor( final Http1Config http1Config, final ConnectionReuseStrategy connReuseStrategy, final Http1StreamListener streamListener) { super(); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.connReuseStrategy = connReuseStrategy != null ? connReuseStrategy : DefaultConnectionReuseStrategy.INSTANCE; this.streamListener = streamListener; } /** * @deprecated Use {@link #HttpRequestExecutor(Http1Config, ConnectionReuseStrategy, Http1StreamListener)} */ @Deprecated public HttpRequestExecutor( final Timeout waitForContinue, final ConnectionReuseStrategy connReuseStrategy, final Http1StreamListener streamListener) { this(Http1Config.custom() .setWaitForContinueTimeout(waitForContinue) .build(), connReuseStrategy, streamListener); } public HttpRequestExecutor(final ConnectionReuseStrategy connReuseStrategy) { this(Http1Config.DEFAULT, connReuseStrategy, null); } public HttpRequestExecutor() { this(Http1Config.DEFAULT, null, null); } /** * Sends the request and obtain a response. * * @param request the request to execute. * @param conn the connection over which to execute the request. * @param informationCallback callback to execute upon receipt of information status (1xx). * May be null. * @param localContext the context * @return the response to the request. * * @throws IOException in case of an I/O error. * @throws HttpException in case of HTTP protocol violation or a processing * problem. */ public ClassicHttpResponse execute( final ClassicHttpRequest request, final HttpClientConnection conn, final HttpResponseInformationCallback informationCallback, final HttpContext localContext) throws IOException, HttpException { Args.notNull(request, "HTTP request"); Args.notNull(conn, "Client connection"); Args.notNull(localContext, "HTTP context"); final HttpCoreContext context = HttpCoreContext.castOrCreate(localContext); try { context.setSSLSession(conn.getSSLSession()); context.setEndpointDetails(conn.getEndpointDetails()); conn.sendRequestHeader(request); if (streamListener != null) { streamListener.onRequestHead(conn, request); } boolean expectContinue = false; final HttpEntity entity = request.getEntity(); if (entity != null) { final Header expect = request.getFirstHeader(HttpHeaders.EXPECT); expectContinue = expect != null && HeaderElements.CONTINUE.equalsIgnoreCase(expect.getValue()); if (!expectContinue) { conn.sendRequestEntity(request); } } conn.flush(); ClassicHttpResponse response = null; while (response == null) { if (expectContinue) { final Timeout timeout = http1Config.getWaitForContinueTimeout() != null ? http1Config.getWaitForContinueTimeout() : DEFAULT_WAIT_FOR_CONTINUE; if (conn.isDataAvailable(timeout)) { response = conn.receiveResponseHeader(); if (streamListener != null) { streamListener.onResponseHead(conn, response); } final int status = response.getCode(); if (status == HttpStatus.SC_CONTINUE) { // discard 100-continue response = null; conn.sendRequestEntity(request); } else if (status < HttpStatus.SC_SUCCESS) { if (informationCallback != null) { informationCallback.execute(response, conn, context); } response = null; continue; } else if (status >= HttpStatus.SC_REDIRECTION) { conn.terminateRequest(request); } else { conn.sendRequestEntity(request); } } else { conn.sendRequestEntity(request); } conn.flush(); expectContinue = false; } else { response = conn.receiveResponseHeader(); if (streamListener != null) { streamListener.onResponseHead(conn, response); } final int status = response.getCode(); if (status < HttpStatus.SC_INFORMATIONAL) { throw new ProtocolException("Invalid response: " + new StatusLine(response)); } if (status < HttpStatus.SC_SUCCESS) { if (informationCallback != null && status != HttpStatus.SC_CONTINUE) { informationCallback.execute(response, conn, context); } response = null; } } } if (MessageSupport.canResponseHaveBody(request.getMethod(), response)) { conn.receiveResponseEntity(response); } return response; } catch (final HttpException | SSLException ex) { Closer.closeQuietly(conn); throw ex; } catch (final SocketTimeoutException ex) { // If the server isn't responsive, we want to close the connection immediately // We set the socket timeout to a minimal value in such a case, because in some cases, the connection // might only have access to an SSLSocket that it will try to close gracefully. conn.setSocketTimeout(Timeout.ONE_MILLISECOND); Closer.close(conn, CloseMode.IMMEDIATE); throw ex; } catch (final IOException | RuntimeException ex) { Closer.close(conn, CloseMode.IMMEDIATE); throw ex; } } /** * Sends the request and obtain a response. * * @param request the request to execute. * @param conn the connection over which to execute the request. * @param context the context * @return the response to the request. * * @throws IOException in case of an I/O error. * @throws HttpException in case of HTTP protocol violation or a processing * problem. */ public ClassicHttpResponse execute( final ClassicHttpRequest request, final HttpClientConnection conn, final HttpContext context) throws IOException, HttpException { return execute(request, conn, null, context); } /** * Pre-process the given request using the given protocol processor and * initiates the process of request execution. * * @param request the request to prepare * @param processor the processor to use * @param localContext the context for sending the request * * @throws IOException in case of an I/O error. * @throws HttpException in case of HTTP protocol violation or a processing * problem. */ public void preProcess( final ClassicHttpRequest request, final HttpProcessor processor, final HttpContext localContext) throws HttpException, IOException { Args.notNull(request, "HTTP request"); Args.notNull(processor, "HTTP processor"); Args.notNull(localContext, "HTTP context"); final ProtocolVersion transportVersion = request.getVersion(); if (transportVersion != null && !transportVersion.lessEquals(http1Config.getVersion())) { throw new UnsupportedHttpVersionException(transportVersion); } final HttpCoreContext context = HttpCoreContext.cast(localContext); context.setProtocolVersion(transportVersion != null ? transportVersion : http1Config.getVersion()); context.setRequest(request); processor.process(request, request.getEntity(), context); } /** * Post-processes the given response using the given protocol processor and * completes the process of request execution. *

* This method does not read the response entity, if any. * The connection over which content of the response entity is being * streamed from cannot be reused until the response entity has been * fully consumed. * * @param response the response object to post-process * @param processor the processor to use * @param localContext the context for post-processing the response * * @throws IOException in case of an I/O error. * @throws HttpException in case of HTTP protocol violation or a processing * problem. */ public void postProcess( final ClassicHttpResponse response, final HttpProcessor processor, final HttpContext localContext) throws HttpException, IOException { Args.notNull(response, "HTTP response"); Args.notNull(processor, "HTTP processor"); Args.notNull(localContext, "HTTP context"); final HttpCoreContext context = HttpCoreContext.cast(localContext); final ProtocolVersion transportVersion = response.getVersion(); if (transportVersion != null) { if (transportVersion.greaterEquals(HttpVersion.HTTP_2)) { throw new UnsupportedHttpVersionException(transportVersion); } context.setProtocolVersion(transportVersion); } context.setResponse(response); processor.process(response, response.getEntity(), context); } /** * Determines whether the connection can be kept alive and is safe to be re-used for subsequent message exchanges. * * @param request current request object. * @param response current response object. * @param connection actual connection. * @param context current context. * @return {@code true} is the connection can be kept-alive and re-used. * @throws IOException in case of an I/O error. */ public boolean keepAlive( final ClassicHttpRequest request, final ClassicHttpResponse response, final HttpClientConnection connection, final HttpContext context) throws IOException { Args.notNull(connection, "HTTP connection"); Args.notNull(request, "HTTP request"); Args.notNull(response, "HTTP response"); Args.notNull(context, "HTTP context"); final boolean keepAlive = connection.isConsistent() && connReuseStrategy.keepAlive(request, response, context); if (streamListener != null) { streamListener.onExchangeComplete(connection, keepAlive); } return keepAlive; } /** * Create a new {@link Builder}. * * @since 5.2 */ public static Builder builder() { return new Builder(); } /** * Builder for {@link HttpRequestExecutor}. * * @since 5.2 */ public static final class Builder { private Timeout waitForContinue; private ConnectionReuseStrategy connReuseStrategy; private Http1StreamListener streamListener; private Builder() { } public Builder withWaitForContinue(final Timeout waitForContinue) { this.waitForContinue = waitForContinue; return this; } public Builder withConnectionReuseStrategy(final ConnectionReuseStrategy connReuseStrategy) { this.connReuseStrategy = connReuseStrategy; return this; } public Builder withHttp1StreamListener(final Http1StreamListener streamListener) { this.streamListener = streamListener; return this; } /** * Create a new HTTP Request Executor. * * @since 5.2 */ public HttpRequestExecutor build() { return new HttpRequestExecutor( waitForContinue, connReuseStrategy, streamListener); } } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpService.java0100644 0000000 0000000 00000037237 00000250600 025221 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequestMapper; import org.apache.hc.core5.http.HttpResponseFactory; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.UnsupportedHttpVersionException; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.ServerSupport; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.HttpServerConnection; import org.apache.hc.core5.http.io.HttpServerRequestHandler; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.io.support.BasicHttpServerExpectationDecorator; import org.apache.hc.core5.http.io.support.BasicHttpServerRequestHandler; import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.util.Args; /** * {@code HttpService} is a server side HTTP protocol handler based on * the classic (blocking) I/O model. *

* {@code HttpService} relies on {@link HttpProcessor} to generate mandatory * protocol headers for all outgoing messages and apply common, cross-cutting * message transformations to all incoming and outgoing messages, whereas * individual {@link HttpRequestHandler}s are expected to implement * application specific content generation and processing. *

* {@code HttpService} uses {@link HttpRequestMapper} to map * matching request handler for a particular request URI of an incoming HTTP * request. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class HttpService { private final HttpProcessor processor; private final Http1Config http1Config; private final HttpServerRequestHandler requestHandler; private final ConnectionReuseStrategy connReuseStrategy; private final Http1StreamListener streamListener; /** * Create a new HTTP service. * @param processor the processor to use on requests and responses * @param handlerMapper the handler mapper * @param responseFactory the response factory. If {@code null} * {@link DefaultClassicHttpResponseFactory#INSTANCE} will be used. * @param connReuseStrategy the connection reuse strategy. If {@code null} * {@link DefaultConnectionReuseStrategy#INSTANCE} will be used. * @param streamListener message stream listener. */ public HttpService( final HttpProcessor processor, final HttpRequestMapper handlerMapper, final ConnectionReuseStrategy connReuseStrategy, final HttpResponseFactory responseFactory, final Http1StreamListener streamListener) { this(processor, new BasicHttpServerExpectationDecorator(new BasicHttpServerRequestHandler(handlerMapper, responseFactory)), Http1Config.DEFAULT, connReuseStrategy, streamListener); } /** * Create a new HTTP service. * * @param processor the processor to use on requests and responses * @param handlerMapper the handler mapper * @param connReuseStrategy the connection reuse strategy. If {@code null} * {@link DefaultConnectionReuseStrategy#INSTANCE} will be used. * @param responseFactory the response factory. If {@code null} * {@link DefaultClassicHttpResponseFactory#INSTANCE} will be used. */ public HttpService( final HttpProcessor processor, final HttpRequestMapper handlerMapper, final ConnectionReuseStrategy connReuseStrategy, final HttpResponseFactory responseFactory) { this(processor, handlerMapper, connReuseStrategy, responseFactory, null); } /** * Create a new HTTP service. * * @param processor the processor to use on requests and responses * @param requestHandler the request handler. * @param connReuseStrategy the connection reuse strategy. If {@code null} * {@link DefaultConnectionReuseStrategy#INSTANCE} will be used. * @param streamListener message stream listener. */ public HttpService( final HttpProcessor processor, final HttpServerRequestHandler requestHandler, final ConnectionReuseStrategy connReuseStrategy, final Http1StreamListener streamListener) { this(processor, requestHandler, Http1Config.DEFAULT, connReuseStrategy, streamListener); } /** * Create a new HTTP service. * * @param processor the processor to use on requests and responses * @param requestHandler the request handler. * @param http1Config HTTP/1 protocol configuration. * @param connReuseStrategy the connection reuse strategy. If {@code null} * {@link DefaultConnectionReuseStrategy#INSTANCE} will be used. * @param streamListener message stream listener. * * @since 5.3 */ public HttpService( final HttpProcessor processor, final HttpServerRequestHandler requestHandler, final Http1Config http1Config, final ConnectionReuseStrategy connReuseStrategy, final Http1StreamListener streamListener) { super(); this.processor = Args.notNull(processor, "HTTP processor"); this.requestHandler = Args.notNull(requestHandler, "Request handler"); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.connReuseStrategy = connReuseStrategy != null ? connReuseStrategy : DefaultConnectionReuseStrategy.INSTANCE; this.streamListener = streamListener; } /** * Create a new HTTP service. * * @param processor the processor to use on requests and responses * @param requestHandler the request handler. */ public HttpService( final HttpProcessor processor, final HttpServerRequestHandler requestHandler) { this(processor, requestHandler, Http1Config.DEFAULT, null, null); } /** * Handles receives one HTTP request over the given connection within the * given execution context and sends a response back to the client. * * @param conn the active connection to the client * @param localContext the actual execution context. * @throws IOException in case of an I/O error. * @throws HttpException in case of HTTP protocol violation or a processing * problem. */ public void handleRequest( final HttpServerConnection conn, final HttpContext localContext) throws IOException, HttpException { final AtomicBoolean responseSubmitted = new AtomicBoolean(); final HttpCoreContext context = HttpCoreContext.cast(localContext); try { final ClassicHttpRequest request = conn.receiveRequestHeader(); if (request == null) { conn.close(); return; } if (streamListener != null) { streamListener.onRequestHead(conn, request); } conn.receiveRequestEntity(request); final ProtocolVersion transportVersion = request.getVersion(); if (transportVersion != null && transportVersion.greaterEquals(HttpVersion.HTTP_2)) { throw new UnsupportedHttpVersionException(transportVersion); } context.setProtocolVersion(transportVersion != null ? transportVersion : http1Config.getVersion()); context.setRequest(request); context.setSSLSession(conn.getSSLSession()); context.setEndpointDetails(conn.getEndpointDetails()); this.processor.process(request, request.getEntity(), context); this.requestHandler.handle(request, new HttpServerRequestHandler.ResponseTrigger() { @Override public void sendInformation(final ClassicHttpResponse response) throws HttpException, IOException { if (responseSubmitted.get()) { throw new HttpException("Response already submitted"); } if (response.getCode() >= HttpStatus.SC_SUCCESS) { throw new HttpException("Invalid intermediate response"); } if (streamListener != null) { streamListener.onResponseHead(conn, response); } conn.sendResponseHeader(response); conn.flush(); } @Override public void submitResponse(final ClassicHttpResponse response) throws HttpException, IOException { try { final ProtocolVersion transportVersion = response.getVersion(); if (transportVersion != null) { if (!transportVersion.lessEquals(http1Config.getVersion())) { throw new UnsupportedHttpVersionException(transportVersion); } context.setProtocolVersion(transportVersion); } context.setResponse(response); processor.process(response, response.getEntity(), context); responseSubmitted.set(true); conn.sendResponseHeader(response); if (streamListener != null) { streamListener.onResponseHead(conn, response); } if (MessageSupport.canResponseHaveBody(request.getMethod(), response)) { conn.sendResponseEntity(response); } // Make sure the request content is fully consumed EntityUtils.consume(request.getEntity()); final boolean keepAlive = connReuseStrategy.keepAlive(request, response, context); if (streamListener != null) { streamListener.onExchangeComplete(conn, keepAlive); } if (!keepAlive) { conn.close(); } conn.flush(); } finally { response.close(); } } }, context); } catch (final HttpException ex) { if (responseSubmitted.get()) { throw ex; } try (final ClassicHttpResponse errorResponse = new BasicClassicHttpResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR)) { handleException(ex, errorResponse); errorResponse.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); context.setResponse(errorResponse); this.processor.process(errorResponse, errorResponse.getEntity(), context); conn.sendResponseHeader(errorResponse); if (streamListener != null) { streamListener.onResponseHead(conn, errorResponse); } conn.sendResponseEntity(errorResponse); conn.close(); } } } /** * Handles the given exception and generates an HTTP response to be sent * back to the client to inform about the exceptional condition encountered * in the course of the request processing. * * @param ex the exception. * @param response the HTTP response. */ protected void handleException(final HttpException ex, final ClassicHttpResponse response) { response.setCode(toStatusCode(ex)); response.setEntity(new StringEntity(ServerSupport.toErrorMessage(ex), ContentType.TEXT_PLAIN)); } protected int toStatusCode(final Exception ex) { return ServerSupport.toStatusCode(ex); } /** * Create a new {@link Builder}. * * @since 5.2 */ public static Builder builder() { return new Builder(); } /** * Builder for {@link HttpService}. * * @since 5.2 */ public static final class Builder { private HttpProcessor processor; private HttpServerRequestHandler requestHandler; private Http1Config http1Config; private ConnectionReuseStrategy connReuseStrategy; private Http1StreamListener streamListener; private Builder() { } public Builder withHttpProcessor(final HttpProcessor processor) { this.processor = processor; return this; } public Builder withHttpServerRequestHandler(final HttpServerRequestHandler requestHandler) { this.requestHandler = requestHandler; return this; } public Builder withHttp1Config(final Http1Config http1Config) { this.http1Config = http1Config; return this; } public Builder withConnectionReuseStrategy(final ConnectionReuseStrategy connReuseStrategy) { this.connReuseStrategy = connReuseStrategy; return this; } public Builder withHttp1StreamListener(final Http1StreamListener streamListener) { this.streamListener = streamListener; return this; } /** * Create a new HTTP service. * * @since 5.2 */ public HttpService build() { return new HttpService( processor, requestHandler, http1Config, connReuseStrategy, streamListener); } } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/IdentityInputStream.java0100644 0000000 0000000 00000006570 00000250600 026742 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.io.InputStream; import org.apache.hc.core5.http.StreamClosedException; import org.apache.hc.core5.http.io.SessionInputBuffer; import org.apache.hc.core5.util.Args; /** * Input stream that reads data without any transformation. The end of the * content entity is demarcated by closing the underlying connection * (EOF condition). Entities transferred using this input stream can be of * unlimited length. *

* Note that this class NEVER closes the underlying stream, even when * {@link #close()} gets called. Instead, the stream will be marked as * closed and no further reading will be permitted. * * @since 4.0 */ public class IdentityInputStream extends InputStream { private final SessionInputBuffer buffer; private final InputStream inputStream; private boolean closed; /** * Default constructor. * * @param buffer Session input buffer * @param inputStream Input stream */ public IdentityInputStream(final SessionInputBuffer buffer, final InputStream inputStream) { super(); this.buffer = Args.notNull(buffer, "Session input buffer"); this.inputStream = Args.notNull(inputStream, "Input stream"); } @Override public int available() throws IOException { if (this.closed) { return 0; } final int n = this.buffer.length(); return n > 0 ? n : this.inputStream.available(); } /** * Marks this stream as closed, but does NOT close the underlying stream. * @throws IOException If an I/O problem occurs. */ @Override public void close() throws IOException { this.closed = true; } @Override public int read() throws IOException { if (this.closed) { throw new StreamClosedException(); } return this.buffer.read(this.inputStream); } @Override public int read(final byte[] b, final int off, final int len) throws IOException { if (this.closed) { throw new StreamClosedException(); } if (len == 0) { return 0; } return this.buffer.read(b, off, len, this.inputStream); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/IdentityOutputStream.java0100644 0000000 0000000 00000006750 00000250600 027143 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.io.OutputStream; import org.apache.hc.core5.http.StreamClosedException; import org.apache.hc.core5.http.io.SessionOutputBuffer; import org.apache.hc.core5.util.Args; /** * Output stream that writes data without any transformation. The end of * the content entity is demarcated by closing the underlying connection * (EOF condition). Entities transferred using this input stream can be of * unlimited length. *

* Note that this class NEVER closes the underlying stream, even when * {@link #close()} gets called. Instead, the stream will be marked as * closed and no further output will be permitted. * * @since 4.0 */ public class IdentityOutputStream extends OutputStream { private final SessionOutputBuffer buffer; private final OutputStream outputStream; /** True if the stream is closed. */ private boolean closed; /** * Default constructor. * * @param buffer Session output buffer * @param outputStream Output stream */ public IdentityOutputStream(final SessionOutputBuffer buffer, final OutputStream outputStream) { super(); this.buffer = Args.notNull(buffer, "Session output buffer"); this.outputStream = Args.notNull(outputStream, "Output stream"); } /** * Finishes writing to the underlying stream, but does NOT close the underlying stream. * @throws IOException If an I/O problem occurs. */ @Override public void close() throws IOException { if (!this.closed) { this.closed = true; this.buffer.flush(this.outputStream); } } @Override public void flush() throws IOException { this.buffer.flush(this.outputStream); } @Override public void write(final byte[] b, final int off, final int len) throws IOException { if (this.closed) { throw new StreamClosedException(); } this.buffer.write(b, off, len, this.outputStream); } @Override public void write(final byte[] b) throws IOException { write(b, 0, b.length); } @Override public void write(final int b) throws IOException { if (this.closed) { throw new StreamClosedException(); } this.buffer.write(b, this.outputStream); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/MonitoringResponseOutOfOrderStrategy.java0100644 0000000 0000000 00000011035 00000250600 032305 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.io.HttpClientConnection; import org.apache.hc.core5.http.io.ResponseOutOfOrderStrategy; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; import java.io.IOException; import java.io.InputStream; /** * A {@link ResponseOutOfOrderStrategy} implementation which checks for premature responses every {@link #chunkSize} * bytes. An 8 KiB chunk size is used by default based on testing using values between 4 KiB and 128 KiB. This is * optimized for correctness and results in a maximum upload speed of 8 MiB/s until {@link #maxChunksToCheck} is * reached. * * @since 5.1 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public final class MonitoringResponseOutOfOrderStrategy implements ResponseOutOfOrderStrategy { private static final int DEFAULT_CHUNK_SIZE = 8 * 1024; public static final MonitoringResponseOutOfOrderStrategy INSTANCE = new MonitoringResponseOutOfOrderStrategy(); private final long chunkSize; private final long maxChunksToCheck; /** * Instantiates a default {@link MonitoringResponseOutOfOrderStrategy}. {@link #INSTANCE} may be used instead. */ public MonitoringResponseOutOfOrderStrategy() { this(DEFAULT_CHUNK_SIZE); } /** * Instantiates a {@link MonitoringResponseOutOfOrderStrategy} with unlimited {@link #maxChunksToCheck}. * * @param chunkSize The chunk size after which a response check is executed. */ public MonitoringResponseOutOfOrderStrategy(final long chunkSize) { this(chunkSize, Long.MAX_VALUE); } /** * Instantiates a {@link MonitoringResponseOutOfOrderStrategy}. * * @param chunkSize The chunk size after which a response check is executed. * @param maxChunksToCheck The maximum number of chunks to check, allowing expensive checks to be avoided * after a sufficient portion of the request entity has been transferred. */ public MonitoringResponseOutOfOrderStrategy(final long chunkSize, final long maxChunksToCheck) { this.chunkSize = Args.positive(chunkSize, "chunkSize"); this.maxChunksToCheck = Args.positive(maxChunksToCheck, "maxChunksToCheck"); } @Override public boolean isEarlyResponseDetected( final ClassicHttpRequest request, final HttpClientConnection connection, final InputStream inputStream, final long totalBytesSent, final long nextWriteSize) throws IOException { if (nextWriteStartsNewChunk(totalBytesSent, nextWriteSize)) { return connection.isDataAvailable(Timeout.ONE_MILLISECOND); } return false; } private boolean nextWriteStartsNewChunk(final long totalBytesSent, final long nextWriteSize) { final long currentChunkIndex = Math.min(totalBytesSent / chunkSize, maxChunksToCheck); final long newChunkIndex = Math.min((totalBytesSent + nextWriteSize) / chunkSize, maxChunksToCheck); return currentChunkIndex < newChunkIndex; } @Override public String toString() { return "DefaultResponseOutOfOrderStrategy{chunkSize=" + chunkSize + ", maxChunksToCheck=" + maxChunksToCheck + '}'; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/NoResponseOutOfOrderStrategy.java0100644 0000000 0000000 00000004503 00000250600 030536 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.io.HttpClientConnection; import org.apache.hc.core5.http.io.ResponseOutOfOrderStrategy; import java.io.InputStream; /** * An implementation of {@link ResponseOutOfOrderStrategy} which does not check for early responses. * * Early response detection requires 1ms blocking reads and incurs a hefty performance cost for * large uploads. * * @see MonitoringResponseOutOfOrderStrategy * @since 5.1 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public final class NoResponseOutOfOrderStrategy implements ResponseOutOfOrderStrategy { public static final NoResponseOutOfOrderStrategy INSTANCE = new NoResponseOutOfOrderStrategy(); @Override public boolean isEarlyResponseDetected( final ClassicHttpRequest request, final HttpClientConnection connection, final InputStream inputStream, final long totalBytesSent, final long nextWriteSize) { return false; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ResponseOutOfOrderException.java0100644 0000000 0000000 00000003106 00000250600 030373 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; /** * Signals an early (out of order) response. */ class ResponseOutOfOrderException extends IOException { /** * Required for serialization support. * * @see java.io.Serializable */ private static final long serialVersionUID = 7802054516041674757L; public ResponseOutOfOrderException() { super(); } }httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/SessionInputBufferImpl.java0100644 0000000 0000000 00000034117 00000250600 027372 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; import org.apache.hc.core5.http.Chars; import org.apache.hc.core5.http.MessageConstraintException; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.io.HttpTransportMetrics; import org.apache.hc.core5.http.io.SessionInputBuffer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.ByteArrayBuffer; import org.apache.hc.core5.util.CharArrayBuffer; /** * Abstract base class for session input buffers that stream data from * an arbitrary {@link InputStream}. This class buffers input data in * an internal byte array for optimal input performance. *

* {@link #readLine(CharArrayBuffer, InputStream)} method of this class treat a lone * LF as valid line delimiters in addition to CR-LF required * by the HTTP specification. * * @since 4.3 */ public class SessionInputBufferImpl implements SessionInputBuffer { private final BasicHttpTransportMetrics metrics; private final byte[] buffer; private final ByteArrayBuffer lineBuffer; private final int minChunkLimit; private final int maxLineLen; private final CharsetDecoder decoder; private int bufferPos; private int bufferLen; private CharBuffer cbuf; /** * Creates new instance of SessionInputBufferImpl. * * @param metrics HTTP transport metrics. * @param bufferSize buffer size. Must be a positive number. * @param minChunkLimit size limit below which data chunks should be buffered in memory * in order to minimize native method invocations on the underlying network socket. * The optimal value of this parameter can be platform specific and defines a trade-off * between performance of memory copy operations and that of native method invocation. * If negative default chunk limited will be used. * @param maxLineLen maximum line length. * @param charDecoder charDecoder to be used for decoding HTTP protocol elements. * If {@code null} simple type cast will be used for byte to char conversion. */ public SessionInputBufferImpl( final BasicHttpTransportMetrics metrics, final int bufferSize, final int minChunkLimit, final int maxLineLen, final CharsetDecoder charDecoder) { Args.notNull(metrics, "HTTP transport metrics"); Args.positive(bufferSize, "Buffer size"); this.metrics = metrics; this.buffer = new byte[bufferSize]; this.bufferPos = 0; this.bufferLen = 0; this.minChunkLimit = minChunkLimit >= 0 ? minChunkLimit : 512; this.maxLineLen = Math.max(maxLineLen, 0); this.lineBuffer = new ByteArrayBuffer(bufferSize); this.decoder = charDecoder; } public SessionInputBufferImpl( final BasicHttpTransportMetrics metrics, final int bufferSize) { this(metrics, bufferSize, bufferSize, 0, null); } public SessionInputBufferImpl(final int bufferSize, final int maxLineLen) { this(new BasicHttpTransportMetrics(), bufferSize, bufferSize, maxLineLen, null); } public SessionInputBufferImpl(final int bufferSize, final CharsetDecoder decoder) { this(new BasicHttpTransportMetrics(), bufferSize, bufferSize, 0, decoder); } public SessionInputBufferImpl(final int bufferSize) { this(new BasicHttpTransportMetrics(), bufferSize, bufferSize, 0, null); } @Override public int capacity() { return this.buffer.length; } @Override public int length() { return this.bufferLen - this.bufferPos; } @Override public int available() { return capacity() - length(); } public int fillBuffer(final InputStream inputStream) throws IOException { Args.notNull(inputStream, "Input stream"); // compact the buffer if necessary if (this.bufferPos > 0) { final int len = this.bufferLen - this.bufferPos; if (len > 0) { System.arraycopy(this.buffer, this.bufferPos, this.buffer, 0, len); } this.bufferPos = 0; this.bufferLen = len; } final int readLen; final int off = this.bufferLen; final int len = this.buffer.length - off; readLen = inputStream.read(this.buffer, off, len); if (readLen == -1) { return -1; } this.bufferLen = off + readLen; this.metrics.incrementBytesTransferred(readLen); return readLen; } public boolean hasBufferedData() { return this.bufferPos < this.bufferLen; } public void clear() { this.bufferPos = 0; this.bufferLen = 0; } @Override public int read(final InputStream inputStream) throws IOException { Args.notNull(inputStream, "Input stream"); int readLen; while (!hasBufferedData()) { readLen = fillBuffer(inputStream); if (readLen == -1) { return -1; } } return this.buffer[this.bufferPos++] & 0xff; } @Override public int read(final byte[] b, final int off, final int len, final InputStream inputStream) throws IOException { Args.notNull(inputStream, "Input stream"); if (b == null || b.length == 0 || len == 0) { return 0; } if (hasBufferedData()) { final int chunk = Math.min(len, this.bufferLen - this.bufferPos); System.arraycopy(this.buffer, this.bufferPos, b, off, chunk); this.bufferPos += chunk; return chunk; } // If the remaining capacity is big enough, read directly from the // underlying input stream bypassing the buffer. if (len > this.minChunkLimit) { final int read = inputStream.read(b, off, len); if (read > 0) { this.metrics.incrementBytesTransferred(read); } return read; } // otherwise read to the buffer first while (!hasBufferedData()) { final int readLen = fillBuffer(inputStream); if (readLen == -1) { return -1; } } final int chunk = Math.min(len, this.bufferLen - this.bufferPos); System.arraycopy(this.buffer, this.bufferPos, b, off, chunk); this.bufferPos += chunk; return chunk; } @Override public int read(final byte[] b, final InputStream inputStream) throws IOException { if (b == null) { return 0; } return read(b, 0, b.length, inputStream); } /** * Reads a complete line of characters up to a line delimiter from this * session buffer into the given line buffer. The number of chars actually * read is returned as an integer. The line delimiter itself is discarded. * If no char 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, end of file is detected, or an exception is thrown. *

* This method treats a lone LF as a valid line delimiters in addition * to CR-LF required by the HTTP specification. * * @param charBuffer the line buffer, one line of characters upon return * @return the total number of bytes read into the buffer, or * {@code -1} is there is no more data because the end of * the stream has been reached. * @throws IOException if an I/O error occurs. */ @Override public int readLine(final CharArrayBuffer charBuffer, final InputStream inputStream) throws IOException { Args.notNull(charBuffer, "Char array buffer"); Args.notNull(inputStream, "Input stream"); int readLen = 0; boolean retry = true; while (retry) { // attempt to find end of line (LF) int pos = -1; for (int i = this.bufferPos; i < this.bufferLen; i++) { if (this.buffer[i] == Chars.LF) { pos = i; break; } } if (this.maxLineLen > 0) { final int currentLen = this.lineBuffer.length() + (pos >= 0 ? pos : this.bufferLen) - this.bufferPos; if (currentLen >= this.maxLineLen) { throw new MessageConstraintException("Maximum line length limit exceeded"); } } if (pos != -1) { // end of line found. if (this.lineBuffer.isEmpty()) { // the entire line is preset in the read buffer return lineFromReadBuffer(charBuffer, pos); } retry = false; final int len = pos + 1 - this.bufferPos; this.lineBuffer.append(this.buffer, this.bufferPos, len); this.bufferPos = pos + 1; } else { // end of line not found if (hasBufferedData()) { final int len = this.bufferLen - this.bufferPos; this.lineBuffer.append(this.buffer, this.bufferPos, len); this.bufferPos = this.bufferLen; } readLen = fillBuffer(inputStream); if (readLen == -1) { retry = false; } } } if (readLen == -1 && this.lineBuffer.isEmpty()) { // indicate the end of stream return -1; } return lineFromLineBuffer(charBuffer); } /** * Reads a complete line of characters up to a line delimiter from this * session buffer. The line delimiter itself is discarded. If no char is * available because the end of the stream has been reached, * {@code null} is returned. This method blocks until input data is * available, end of file is detected, or an exception is thrown. *

* This method treats a lone LF as a valid line delimiters in addition * to CR-LF required by the HTTP specification. * * @return HTTP line as a string * @throws IOException if an I/O error occurs. */ private int lineFromLineBuffer(final CharArrayBuffer charBuffer) throws IOException { // discard LF if found int len = this.lineBuffer.length(); if (len > 0) { if (this.lineBuffer.byteAt(len - 1) == Chars.LF) { len--; } // discard CR if found if (len > 0 && this.lineBuffer.byteAt(len - 1) == Chars.CR) { len--; } } if (this.decoder == null) { charBuffer.append(this.lineBuffer, 0, len); } else { final ByteBuffer bbuf = ByteBuffer.wrap(this.lineBuffer.array(), 0, len); len = appendDecoded(charBuffer, bbuf); } this.lineBuffer.clear(); return len; } private int lineFromReadBuffer(final CharArrayBuffer charbuffer, final int position) throws IOException { int pos = position; final int off = this.bufferPos; int len; this.bufferPos = pos + 1; if (pos > off && this.buffer[pos - 1] == Chars.CR) { // skip CR if found pos--; } len = pos - off; if (this.decoder == null) { charbuffer.append(this.buffer, off, len); } else { final ByteBuffer bbuf = ByteBuffer.wrap(this.buffer, off, len); len = appendDecoded(charbuffer, bbuf); } return len; } private int appendDecoded( final CharArrayBuffer charbuffer, final ByteBuffer bbuf) throws IOException { if (!bbuf.hasRemaining()) { return 0; } if (this.cbuf == null) { this.cbuf = CharBuffer.allocate(1024); } this.decoder.reset(); int len = 0; while (bbuf.hasRemaining()) { final CoderResult result = this.decoder.decode(bbuf, this.cbuf, true); len += handleDecodingResult(result, charbuffer); } final CoderResult result = this.decoder.flush(this.cbuf); len += handleDecodingResult(result, charbuffer); this.cbuf.clear(); return len; } private int handleDecodingResult( final CoderResult result, final CharArrayBuffer charBuffer) throws IOException { if (result.isError()) { result.throwException(); } this.cbuf.flip(); final int len = this.cbuf.remaining(); while (this.cbuf.hasRemaining()) { charBuffer.append(this.cbuf.get()); } this.cbuf.compact(); return len; } @Override public HttpTransportMetrics getMetrics() { return this.metrics; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/SessionOutputBufferImpl.java0100644 0000000 0000000 00000021323 00000250600 027566 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import org.apache.hc.core5.http.Chars; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.io.HttpTransportMetrics; import org.apache.hc.core5.http.io.SessionOutputBuffer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.ByteArrayBuffer; import org.apache.hc.core5.util.CharArrayBuffer; /** * Abstract base class for session output buffers that stream data to * an arbitrary {@link OutputStream}. This class buffers small chunks of * output data in an internal byte array for optimal output performance. *

* {@link #writeLine(CharArrayBuffer, OutputStream)} method of this class uses CR-LF * as a line delimiter. * * @since 4.3 */ public class SessionOutputBufferImpl implements SessionOutputBuffer { private static final byte[] CRLF = new byte[] {Chars.CR, Chars.LF}; private final BasicHttpTransportMetrics metrics; private final ByteArrayBuffer buffer; private final int fragmentSizeHint; private final CharsetEncoder encoder; private ByteBuffer bbuf; /** * Creates new instance of SessionOutputBufferImpl. * * @param metrics HTTP transport metrics. * @param bufferSize buffer size. Must be a positive number. * @param fragmentSizeHint fragment size hint defining a minimal size of a fragment * that should be written out directly to the socket bypassing the session buffer. * Value {@code 0} disables fragment buffering. * @param charEncoder charEncoder to be used for encoding HTTP protocol elements. * If {@code null} simple type cast will be used for char to byte conversion. */ public SessionOutputBufferImpl( final BasicHttpTransportMetrics metrics, final int bufferSize, final int fragmentSizeHint, final CharsetEncoder charEncoder) { super(); Args.positive(bufferSize, "Buffer size"); Args.notNull(metrics, "HTTP transport metrics"); this.metrics = metrics; this.buffer = new ByteArrayBuffer(bufferSize); this.fragmentSizeHint = fragmentSizeHint >= 0 ? fragmentSizeHint : bufferSize; this.encoder = charEncoder; } public SessionOutputBufferImpl(final int bufferSize) { this(new BasicHttpTransportMetrics(), bufferSize, bufferSize, null); } public SessionOutputBufferImpl(final int bufferSize, final CharsetEncoder encoder) { this(new BasicHttpTransportMetrics(), bufferSize, bufferSize, encoder); } @Override public int capacity() { return this.buffer.capacity(); } @Override public int length() { return this.buffer.length(); } @Override public int available() { return capacity() - length(); } private void flushBuffer(final OutputStream outputStream) throws IOException { final int len = this.buffer.length(); if (len > 0) { outputStream.write(this.buffer.array(), 0, len); this.buffer.clear(); this.metrics.incrementBytesTransferred(len); } } @Override public void flush(final OutputStream outputStream) throws IOException { Args.notNull(outputStream, "Output stream"); flushBuffer(outputStream); outputStream.flush(); } @Override public void write(final byte[] b, final int off, final int len, final OutputStream outputStream) throws IOException { if (b == null) { return; } Args.notNull(outputStream, "Output stream"); // Do not want to buffer large-ish chunks // if the byte array is larger then MIN_CHUNK_LIMIT // write it directly to the output stream if (len > this.fragmentSizeHint || len > this.buffer.capacity()) { // flush the buffer flushBuffer(outputStream); // write directly to the out stream outputStream.write(b, off, len); this.metrics.incrementBytesTransferred(len); } else { // Do not let the buffer grow unnecessarily final int freecapacity = this.buffer.capacity() - this.buffer.length(); if (len > freecapacity) { // flush the buffer flushBuffer(outputStream); } // buffer this.buffer.append(b, off, len); } } @Override public void write(final byte[] b, final OutputStream outputStream) throws IOException { if (b == null) { return; } write(b, 0, b.length, outputStream); } @Override public void write(final int b, final OutputStream outputStream) throws IOException { Args.notNull(outputStream, "Output stream"); if (this.fragmentSizeHint > 0) { if (this.buffer.isFull()) { flushBuffer(outputStream); } this.buffer.append(b); } else { flushBuffer(outputStream); outputStream.write(b); } } /** * Writes characters from the specified char array followed by a line * delimiter to this session buffer. *

* This method uses CR-LF as a line delimiter. * * @param charbuffer the buffer containing chars of the line. * @throws IOException if an I/O error occurs. */ @Override public void writeLine(final CharArrayBuffer charbuffer, final OutputStream outputStream) throws IOException { if (charbuffer == null) { return; } Args.notNull(outputStream, "Output stream"); if (this.encoder == null) { int off = 0; int remaining = charbuffer.length(); while (remaining > 0) { int chunk = this.buffer.capacity() - this.buffer.length(); chunk = Math.min(chunk, remaining); if (chunk > 0) { this.buffer.append(charbuffer, off, chunk); } if (this.buffer.isFull()) { flushBuffer(outputStream); } off += chunk; remaining -= chunk; } } else { final CharBuffer cbuf = CharBuffer.wrap(charbuffer.array(), 0, charbuffer.length()); writeEncoded(cbuf, outputStream); } write(CRLF, outputStream); } private void writeEncoded(final CharBuffer cbuf, final OutputStream outputStream) throws IOException { if (!cbuf.hasRemaining()) { return; } if (this.bbuf == null) { this.bbuf = ByteBuffer.allocate(1024); } this.encoder.reset(); while (cbuf.hasRemaining()) { final CoderResult result = this.encoder.encode(cbuf, this.bbuf, true); handleEncodingResult(result, outputStream); } final CoderResult result = this.encoder.flush(this.bbuf); handleEncodingResult(result, outputStream); this.bbuf.clear(); } private void handleEncodingResult(final CoderResult result, final OutputStream outputStream) throws IOException { if (result.isError()) { result.throwException(); } this.bbuf.flip(); while (this.bbuf.hasRemaining()) { write(this.bbuf.get(), outputStream); } this.bbuf.compact(); } @Override public HttpTransportMetrics getMetrics() { return this.metrics; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/SocketHolder.java0100644 0000000 0000000 00000007703 00000250600 025342 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLSocket; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.util.Args; /** * Utility class that holds a {@link Socket} along with copies of its {@link InputStream} * and {@link OutputStream}. * * @since 5.0 */ public class SocketHolder { private final SSLSocket sslSocket; private final Socket baseSocket; private final AtomicReference inputStreamRef; private final AtomicReference outputStreamRef; /** * @since 5.3 */ public SocketHolder(final SSLSocket sslSocket, final Socket baseSocket) { this.sslSocket = Args.notNull(sslSocket, "SSL Socket"); this.baseSocket = Args.notNull(baseSocket, "Socket"); this.inputStreamRef = new AtomicReference<>(); this.outputStreamRef = new AtomicReference<>(); } public SocketHolder(final Socket socket) { this.baseSocket = Args.notNull(socket, "Socket"); this.sslSocket = null; this.inputStreamRef = new AtomicReference<>(); this.outputStreamRef = new AtomicReference<>(); } public final Socket getSocket() { return sslSocket != null ? sslSocket : baseSocket; } /** * @since 5.3 */ @Internal public Socket getBaseSocket() { return baseSocket; } /** * @since 5.3 */ @Internal public SSLSocket getSSLSocket() { return sslSocket; } public final InputStream getInputStream() throws IOException { InputStream local = inputStreamRef.get(); if (local != null) { return local; } local = getInputStream(getSocket()); if (inputStreamRef.compareAndSet(null, local)) { return local; } return inputStreamRef.get(); } protected InputStream getInputStream(final Socket socket) throws IOException { return socket.getInputStream(); } protected OutputStream getOutputStream(final Socket socket) throws IOException { return socket.getOutputStream(); } public final OutputStream getOutputStream() throws IOException { OutputStream local = outputStreamRef.get(); if (local != null) { return local; } local = getOutputStream(getSocket()); if (outputStreamRef.compareAndSet(null, local)) { return local; } return outputStreamRef.get(); } @Override public String toString() { return "SocketHolder{" + "sslSocket=" + sslSocket + ", baseSocket=" + baseSocket + '}'; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/package-info.java0100644 0000000 0000000 00000002461 00000250600 025274 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Default implementation of HTTP/1.1 transport based on the classic * (blocking) I/O model. */ package org.apache.hc.core5.http.impl.io; httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/support/0040755 0000000 0000000 00000000000 00000250600 023621 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/support/IncomingHttpEntity.java0100644 0000000 0000000 00000010052 00000250600 030257 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io.support; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Collections; import java.util.List; import java.util.Set; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpMessage; import org.apache.hc.core5.http.io.entity.AbstractHttpEntity; import org.apache.hc.core5.io.Closer; @Internal public class IncomingHttpEntity implements HttpEntity { private final InputStream content; private final long len; private final HttpMessage message; public IncomingHttpEntity(final InputStream content, final long len, final HttpMessage message) { this.content = content; this.len = len; this.message = message; } @Override public boolean isRepeatable() { return false; } @Override public boolean isChunked() { return len == ContentLengthStrategy.CHUNKED; } @Override public long getContentLength() { return len >= 0 ? len : -1; } @Override public String getContentType() { final Header h = message.getFirstHeader(HttpHeaders.CONTENT_TYPE); return h != null ? h.getValue() : null; } @Override public String getContentEncoding() { final Header h = message.getFirstHeader(HttpHeaders.CONTENT_ENCODING); return h != null ? h.getValue() : null; } @Override public InputStream getContent() throws IOException, IllegalStateException { return content; } @Override public boolean isStreaming() { return content != null; } @Override public void writeTo(final OutputStream outStream) throws IOException { AbstractHttpEntity.writeTo(this, outStream); } @Override public Supplier> getTrailers() { return null; } @Override public Set getTrailerNames() { return Collections.emptySet(); } @Override public void close() throws IOException { Closer.close(content); } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append('['); sb.append("Content-Type: "); sb.append(getContentType()); sb.append(','); sb.append("Content-Encoding: "); sb.append(getContentEncoding()); sb.append(','); final long len = getContentLength(); if (len >= 0) { sb.append("Content-Length: "); sb.append(len); sb.append(','); } sb.append("Chunked: "); sb.append(isChunked()); sb.append(']'); return sb.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/0040755 0000000 0000000 00000000000 00000250600 022263 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractContentDecoder.java0100644 0000000 0000000 00000014062 00000250600 027512 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.util.List; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.nio.ContentDecoder; import org.apache.hc.core5.http.nio.SessionInputBuffer; import org.apache.hc.core5.util.Args; /** * Abstract {@link ContentDecoder} that serves as a base for all content * decoder implementations. * * @since 4.0 */ public abstract class AbstractContentDecoder implements ContentDecoder { final ReadableByteChannel channel; final SessionInputBuffer buffer; final BasicHttpTransportMetrics metrics; protected boolean completed; /** * Creates an instance of this class. * * @param channel the source channel. * @param buffer the session input buffer that can be used to store * session data for intermediate processing. * @param metrics Transport metrics of the underlying HTTP transport. */ public AbstractContentDecoder( final ReadableByteChannel channel, final SessionInputBuffer buffer, final BasicHttpTransportMetrics metrics) { super(); Args.notNull(channel, "Channel"); Args.notNull(buffer, "Session input buffer"); Args.notNull(metrics, "Transport metrics"); this.buffer = buffer; this.channel = channel; this.metrics = metrics; } protected ReadableByteChannel channel() { return this.channel; } protected SessionInputBuffer buffer() { return this.buffer; } protected BasicHttpTransportMetrics metrics() { return this.metrics; } @Override public boolean isCompleted() { return this.completed; } /** * Sets the completed status of this decoder. Normally this is not necessary * (the decoder will automatically complete when the underlying channel * returns EOF). It is useful to mark the decoder as completed if you have * some other means to know all the necessary data has been read and want to * reuse the underlying connection for more messages. * * @param completed the completed status of this decoder. * @since 4.4.11 */ public void setCompleted(final boolean completed) { this.completed = completed; } /** * Sets the completed status of this decoder to true. Normally this is not necessary * (the decoder will automatically complete when the underlying channel * returns EOF). It is useful to mark the decoder as completed if you have * some other means to know all the necessary data has been read and want to * reuse the underlying connection for more messages. * * @since 4.4.11 */ protected void setCompleted() { this.completed = true; } /** * Reads from the channel to the destination. * * @param dst destination. * @return number of bytes transferred. * @throws IOException If an I/O error occurs. * * @since 4.3 */ protected int readFromChannel(final ByteBuffer dst) throws IOException { final int bytesRead = this.channel.read(dst); if (bytesRead > 0) { this.metrics.incrementBytesTransferred(bytesRead); } return bytesRead; } /** * Reads from the channel to the session buffer. * * @return number of bytes transferred, possibly zero, or {@code -1} if the channel has reached end-of-stream. * @throws IOException If an I/O error occurs. * * @since 4.3 */ protected int fillBufferFromChannel() throws IOException { final int bytesRead = this.buffer.fill(this.channel); if (bytesRead > 0) { this.metrics.incrementBytesTransferred(bytesRead); } return bytesRead; } /** * Reads from the channel to the destination. * * @param dst destination. * @param limit max number of bytes to transfer. * @return number of bytes transferred, possibly zero, or {@code -1} if the channel has reached end-of-stream. * @throws IOException If an I/O error occurs. * * @since 4.3 */ protected int readFromChannel(final ByteBuffer dst, final int limit) throws IOException { final int bytesRead; if (dst.remaining() > limit) { final int oldLimit = dst.limit(); final int newLimit = oldLimit - (dst.remaining() - limit); dst.limit(newLimit); bytesRead = this.channel.read(dst); dst.limit(oldLimit); } else { bytesRead = this.channel.read(dst); } if (bytesRead > 0) { this.metrics.incrementBytesTransferred(bytesRead); } return bytesRead; } @Override public List getTrailers() { return null; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractContentEncoder.java0100644 0000000 0000000 00000014525 00000250600 027530 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.util.List; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.nio.ContentEncoder; import org.apache.hc.core5.http.nio.SessionOutputBuffer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; /** * Abstract {@link ContentEncoder} that serves as a base for all content * encoder implementations. * * @since 4.0 */ public abstract class AbstractContentEncoder implements ContentEncoder { final WritableByteChannel channel; final SessionOutputBuffer buffer; final BasicHttpTransportMetrics metrics; boolean completed; /** * Creates an instance of this class. * * @param channel the destination channel. * @param buffer the session output buffer that can be used to store * session data for intermediate processing. * @param metrics Transport metrics of the underlying HTTP transport. */ public AbstractContentEncoder( final WritableByteChannel channel, final SessionOutputBuffer buffer, final BasicHttpTransportMetrics metrics) { super(); Args.notNull(channel, "Channel"); Args.notNull(buffer, "Session input buffer"); Args.notNull(metrics, "Transport metrics"); this.buffer = buffer; this.channel = channel; this.metrics = metrics; } protected WritableByteChannel channel() { return this.channel; } protected SessionOutputBuffer buffer() { return this.buffer; } protected BasicHttpTransportMetrics metrics() { return this.metrics; } @Override public boolean isCompleted() { return this.completed; } @Override public void complete(final List trailers) throws IOException { this.completed = true; } public final void complete() throws IOException { complete(null); } protected void assertNotCompleted() { Asserts.check(!this.completed, "Encoding process already completed"); } /** * Flushes content of the session buffer to the channel and updates transport metrics. * * @return number of bytes written to the channel. * @throws IOException in case of an I/O error. * @since 4.3 */ protected int flushToChannel() throws IOException { if (!this.buffer.hasData()) { return 0; } final int bytesWritten = this.buffer.flush(this.channel); if (bytesWritten > 0) { this.metrics.incrementBytesTransferred(bytesWritten); } return bytesWritten; } /** * Flushes content of the given buffer to the channel and updates transport metrics. * * @return number of bytes written to the channel. * @throws IOException in case of an I/O error. * @since 4.3 */ protected int writeToChannel(final ByteBuffer src) throws IOException { if (!src.hasRemaining()) { return 0; } final int bytesWritten = this.channel.write(src); if (bytesWritten > 0) { this.metrics.incrementBytesTransferred(bytesWritten); } return bytesWritten; } /** * Transfers content of the source to the channel and updates transport metrics. * * @param src source. * @param limit max number of bytes to transfer. * @return number of bytes transferred. * @throws IOException in case of an I/O error. * @since 4.3 */ protected int writeToChannel(final ByteBuffer src, final int limit) throws IOException { return doWriteChunk(src, limit, true); } /** * Transfers content of the source to the buffer and updates transport metrics. * * @param src source. * @param limit max number of bytes to transfer. * @return number of bytes transferred. * @throws IOException in case of an I/O error. * @since 4.3 */ protected int writeToBuffer(final ByteBuffer src, final int limit) throws IOException { return doWriteChunk(src, limit, false); } private int doWriteChunk( final ByteBuffer src, final int chunk, final boolean direct) throws IOException { final int bytesWritten; if (src.remaining() > chunk) { final int oldLimit = src.limit(); final int newLimit = oldLimit - (src.remaining() - chunk); src.limit(newLimit); bytesWritten = doWriteChunk(src, direct); src.limit(oldLimit); } else { bytesWritten = doWriteChunk(src, direct); } return bytesWritten; } private int doWriteChunk(final ByteBuffer src, final boolean direct) throws IOException { if (direct) { final int bytesWritten = this.channel.write(src); if (bytesWritten > 0) { this.metrics.incrementBytesTransferred(bytesWritten); } return bytesWritten; } final int chunk = src.remaining(); this.buffer.write(src); return chunk; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractHttp1IOEventHandler.java0100644 0000000 0000000 00000010451 00000250600 030340 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; import javax.net.ssl.SSLSession; import org.apache.hc.core5.http.EndpointDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; class AbstractHttp1IOEventHandler implements HttpConnectionEventHandler { final AbstractHttp1StreamDuplexer streamDuplexer; AbstractHttp1IOEventHandler(final AbstractHttp1StreamDuplexer streamDuplexer) { this.streamDuplexer = Args.notNull(streamDuplexer, "Stream multiplexer"); } @Override public void connected(final IOSession session) throws IOException { try { streamDuplexer.onConnect(); } catch (final HttpException ex) { streamDuplexer.onException(ex); } } @Override public void inputReady(final IOSession session, final ByteBuffer src) throws IOException { try { streamDuplexer.onInput(src); } catch (final HttpException ex) { streamDuplexer.onException(ex); } } @Override public void outputReady(final IOSession session) throws IOException { try { streamDuplexer.onOutput(); } catch (final HttpException ex) { streamDuplexer.onException(ex); } } @Override public void timeout(final IOSession session, final Timeout timeout) throws IOException { try { streamDuplexer.onTimeout(timeout); } catch (final HttpException ex) { streamDuplexer.onException(ex); } } @Override public void exception(final IOSession session, final Exception cause) { streamDuplexer.onException(cause); } @Override public void disconnected(final IOSession session) { streamDuplexer.onDisconnect(); } @Override public void close() throws IOException { streamDuplexer.close(); } @Override public void close(final CloseMode closeMode) { streamDuplexer.close(closeMode); } @Override public boolean isOpen() { return streamDuplexer.isOpen(); } @Override public void setSocketTimeout(final Timeout timeout) { streamDuplexer.setSocketTimeout(timeout); } @Override public SSLSession getSSLSession() { return streamDuplexer.getSSLSession(); } @Override public EndpointDetails getEndpointDetails() { return streamDuplexer.getEndpointDetails(); } @Override public Timeout getSocketTimeout() { return streamDuplexer.getSocketTimeout(); } @Override public ProtocolVersion getProtocolVersion() { return streamDuplexer.getProtocolVersion(); } @Override public SocketAddress getRemoteAddress() { return streamDuplexer.getRemoteAddress(); } @Override public SocketAddress getLocalAddress() { return streamDuplexer.getLocalAddress(); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractHttp1StreamDuplexer.java0100644 0000000 0000000 00000064061 00000250600 030503 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.nio.channels.SelectionKey; import java.nio.channels.WritableByteChannel; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.EndpointDetails; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpMessage; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.BasicEndpointDetails; import org.apache.hc.core5.http.impl.BasicHttpConnectionMetrics; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.impl.CharCodingSupport; import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy; import org.apache.hc.core5.http.impl.IncomingEntityDetails; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.ContentDecoder; import org.apache.hc.core5.http.nio.ContentEncoder; import org.apache.hc.core5.http.nio.NHttpMessageParser; import org.apache.hc.core5.http.nio.NHttpMessageWriter; import org.apache.hc.core5.http.nio.SessionInputBuffer; import org.apache.hc.core5.http.nio.SessionOutputBuffer; import org.apache.hc.core5.http.nio.command.CommandSupport; import org.apache.hc.core5.http.nio.command.RequestExecutionCommand; import org.apache.hc.core5.http.nio.command.ShutdownCommand; import org.apache.hc.core5.http.nio.command.StaleCheckCommand; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.SocketTimeoutExceptionFactory; import org.apache.hc.core5.reactor.Command; import org.apache.hc.core5.reactor.EventMask; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.reactor.ssl.TlsDetails; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Identifiable; import org.apache.hc.core5.util.Timeout; abstract class AbstractHttp1StreamDuplexer implements Identifiable, HttpConnection { private enum ConnectionState { READY, ACTIVE, GRACEFUL_SHUTDOWN, SHUTDOWN } private final ProtocolIOSession ioSession; private final Http1Config http1Config; private final SessionInputBufferImpl inbuf; private final SessionOutputBufferImpl outbuf; private final BasicHttpTransportMetrics inTransportMetrics; private final BasicHttpTransportMetrics outTransportMetrics; private final BasicHttpConnectionMetrics connMetrics; private final NHttpMessageParser incomingMessageParser; private final NHttpMessageWriter outgoingMessageWriter; private final ContentLengthStrategy incomingContentStrategy; private final ContentLengthStrategy outgoingContentStrategy; private final ByteBuffer contentBuffer; private final AtomicInteger outputRequests; private volatile Message incomingMessage; private volatile Message outgoingMessage; private volatile ConnectionState connState; private volatile CapacityWindow capacityWindow; private volatile ProtocolVersion version; private volatile EndpointDetails endpointDetails; AbstractHttp1StreamDuplexer( final ProtocolIOSession ioSession, final Http1Config http1Config, final CharCodingConfig charCodingConfig, final NHttpMessageParser incomingMessageParser, final NHttpMessageWriter outgoingMessageWriter, final ContentLengthStrategy incomingContentStrategy, final ContentLengthStrategy outgoingContentStrategy) { this.ioSession = Args.notNull(ioSession, "I/O session"); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; final int bufferSize = this.http1Config.getBufferSize(); this.inbuf = new SessionInputBufferImpl(bufferSize, Math.min(bufferSize, 512), this.http1Config.getMaxLineLength(), CharCodingSupport.createDecoder(charCodingConfig)); this.outbuf = new SessionOutputBufferImpl(bufferSize, Math.min(bufferSize, 512), CharCodingSupport.createEncoder(charCodingConfig)); this.inTransportMetrics = new BasicHttpTransportMetrics(); this.outTransportMetrics = new BasicHttpTransportMetrics(); this.connMetrics = new BasicHttpConnectionMetrics(inTransportMetrics, outTransportMetrics); this.incomingMessageParser = incomingMessageParser; this.outgoingMessageWriter = outgoingMessageWriter; this.incomingContentStrategy = incomingContentStrategy != null ? incomingContentStrategy : DefaultContentLengthStrategy.INSTANCE; this.outgoingContentStrategy = outgoingContentStrategy != null ? outgoingContentStrategy : DefaultContentLengthStrategy.INSTANCE; this.contentBuffer = ByteBuffer.allocate(this.http1Config.getBufferSize()); this.outputRequests = new AtomicInteger(0); this.connState = ConnectionState.READY; } @Override public String getId() { return ioSession.getId(); } boolean isActive() { return connState == ConnectionState.ACTIVE; } boolean isShuttingDown() { return connState.compareTo(ConnectionState.GRACEFUL_SHUTDOWN) >= 0; } void shutdownSession(final CloseMode closeMode) { if (closeMode == CloseMode.GRACEFUL) { connState = ConnectionState.GRACEFUL_SHUTDOWN; ioSession.enqueue(ShutdownCommand.GRACEFUL, Command.Priority.NORMAL); } else { connState = ConnectionState.SHUTDOWN; ioSession.close(); } } void shutdownSession(final Exception cause) { connState = ConnectionState.SHUTDOWN; try { terminate(cause); } finally { final CloseMode closeMode; if (cause instanceof ConnectionClosedException) { closeMode = CloseMode.GRACEFUL; } else if (cause instanceof SSLHandshakeException) { closeMode = CloseMode.GRACEFUL; } else if (cause instanceof IOException) { closeMode = CloseMode.IMMEDIATE; } else { closeMode = CloseMode.GRACEFUL; } ioSession.close(closeMode); } } abstract void disconnected(); abstract void terminate(final Exception exception); abstract void updateInputMetrics(IncomingMessage incomingMessage, BasicHttpConnectionMetrics connMetrics); abstract void updateOutputMetrics(OutgoingMessage outgoingMessage, BasicHttpConnectionMetrics connMetrics); abstract void consumeHeader(IncomingMessage messageHead, EntityDetails entityDetails) throws HttpException, IOException; abstract boolean handleIncomingMessage(IncomingMessage incomingMessage) throws HttpException; abstract boolean handleOutgoingMessage(OutgoingMessage outgoingMessage) throws HttpException; abstract ContentDecoder createContentDecoder( long contentLength, ReadableByteChannel channel, SessionInputBuffer buffer, BasicHttpTransportMetrics metrics) throws HttpException; abstract ContentEncoder createContentEncoder( long contentLength, WritableByteChannel channel, SessionOutputBuffer buffer, BasicHttpTransportMetrics metrics) throws HttpException; abstract void consumeData(ByteBuffer src) throws HttpException, IOException; abstract void updateCapacity(CapacityChannel capacityChannel) throws HttpException, IOException; abstract void dataEnd(List trailers) throws HttpException, IOException; abstract boolean isOutputReady(); abstract boolean isRequestInitiated(); abstract void produceOutput() throws HttpException, IOException; abstract void execute(RequestExecutionCommand executionCommand) throws HttpException, IOException; abstract void inputEnd() throws HttpException, IOException; abstract void outputEnd() throws HttpException, IOException; abstract boolean inputIdle(); abstract boolean outputIdle(); abstract boolean handleTimeout(); private void processCommands() throws HttpException, IOException { for (;;) { final Command command = ioSession.poll(); if (command == null) { return; } if (command instanceof ShutdownCommand) { final ShutdownCommand shutdownCommand = (ShutdownCommand) command; requestShutdown(shutdownCommand.getType()); } else if (command instanceof RequestExecutionCommand) { if (connState.compareTo(ConnectionState.GRACEFUL_SHUTDOWN) >= 0) { command.cancel(); } else { execute((RequestExecutionCommand) command); return; } } else if (command instanceof StaleCheckCommand) { doStaleCheck(((StaleCheckCommand) command).getCallback()); } else { throw new HttpException("Unexpected command: " + command.getClass()); } } } public final void onConnect() throws HttpException, IOException { if (connState == ConnectionState.READY) { connState = ConnectionState.ACTIVE; processCommands(); } } IncomingMessage parseMessageHead(final boolean endOfStream) throws IOException, HttpException { final IncomingMessage messageHead = incomingMessageParser.parse(inbuf, endOfStream); if (messageHead != null) { incomingMessageParser.reset(); } return messageHead; } public final void onInput(final ByteBuffer src) throws HttpException, IOException { if (src != null) { final int n = src.remaining(); inbuf.put(src); inTransportMetrics.incrementBytesTransferred(n); } if (connState.compareTo(ConnectionState.GRACEFUL_SHUTDOWN) >= 0 && !inbuf.hasData() && inputIdle()) { ioSession.clearEvent(SelectionKey.OP_READ); return; } boolean endOfStream = false; do { if (incomingMessage == null) { final int bytesRead = inbuf.fill(ioSession); if (bytesRead > 0) { inTransportMetrics.incrementBytesTransferred(bytesRead); } endOfStream = bytesRead == -1; final IncomingMessage messageHead = parseMessageHead(endOfStream); if (messageHead != null) { this.version = messageHead.getVersion(); updateInputMetrics(messageHead, connMetrics); final ContentDecoder contentDecoder; if (handleIncomingMessage(messageHead)) { final long len = incomingContentStrategy.determineLength(messageHead); contentDecoder = createContentDecoder(len, ioSession, inbuf, inTransportMetrics); consumeHeader(messageHead, contentDecoder != null ? new IncomingEntityDetails(messageHead, len) : null); } else { consumeHeader(messageHead, null); contentDecoder = null; } capacityWindow = new CapacityWindow(http1Config.getInitialWindowSize(), ioSession); if (contentDecoder != null) { incomingMessage = new Message<>(messageHead, contentDecoder); } else { inputEnd(); if (connState.compareTo(ConnectionState.ACTIVE) == 0) { ioSession.setEvent(SelectionKey.OP_READ); } } } else { break; } } if (incomingMessage != null) { final ContentDecoder contentDecoder = incomingMessage.getBody(); // At present the consumer can be forced to consume data // over its declared capacity in order to avoid having // unprocessed message body content stuck in the session // input buffer final int bytesRead = contentDecoder.read(contentBuffer); if (bytesRead > 0) { contentBuffer.flip(); consumeData(contentBuffer); contentBuffer.clear(); final int capacity = capacityWindow.removeCapacity(bytesRead); if (capacity <= 0) { if (!contentDecoder.isCompleted()) { updateCapacity(capacityWindow); } } } if (contentDecoder.isCompleted()) { dataEnd(contentDecoder.getTrailers()); capacityWindow.close(); incomingMessage = null; ioSession.setEvent(SelectionKey.OP_READ); inputEnd(); } else if (bytesRead == 0) { break; } } } while (inbuf.hasData()); if (endOfStream && !inbuf.hasData()) { if (inputIdle()) { requestShutdown(CloseMode.GRACEFUL); } else { shutdownSession(new ConnectionClosedException("Connection closed by peer")); } } } public final void onOutput() throws IOException, HttpException { ioSession.getLock().lock(); try { if (outbuf.hasData()) { final int bytesWritten = outbuf.flush(ioSession); if (bytesWritten > 0) { outTransportMetrics.incrementBytesTransferred(bytesWritten); } } } finally { ioSession.getLock().unlock(); } if (connState.compareTo(ConnectionState.SHUTDOWN) < 0) { final int pendingOutputRequests = outputRequests.get(); produceOutput(); final boolean outputPending = isOutputReady(); final boolean outputEnd; ioSession.getLock().lock(); try { if (!outputPending && !outbuf.hasData() && outputRequests.compareAndSet(pendingOutputRequests, 0)) { ioSession.clearEvent(SelectionKey.OP_WRITE); } else { outputRequests.addAndGet(-pendingOutputRequests); } outputEnd = outgoingMessage == null && !outbuf.hasData() && !isRequestInitiated(); } finally { ioSession.getLock().unlock(); } if (outputEnd) { outputEnd(); if (connState.compareTo(ConnectionState.ACTIVE) == 0) { processCommands(); } else if (connState.compareTo(ConnectionState.GRACEFUL_SHUTDOWN) >= 0 && inputIdle() && outputIdle()) { connState = ConnectionState.SHUTDOWN; } } } if (connState.compareTo(ConnectionState.SHUTDOWN) >= 0) { ioSession.close(); } } public final void onTimeout(final Timeout timeout) throws IOException, HttpException { if (!handleTimeout()) { onException(SocketTimeoutExceptionFactory.create(timeout)); } } public final void onException(final Exception ex) { shutdownSession(ex); CommandSupport.failCommands(ioSession, ex); } public final void onDisconnect() { disconnected(); CommandSupport.cancelCommands(ioSession); } void requestShutdown(final CloseMode closeMode) { switch (closeMode) { case GRACEFUL: if (connState == ConnectionState.ACTIVE) { connState = ConnectionState.GRACEFUL_SHUTDOWN; } break; case IMMEDIATE: connState = ConnectionState.SHUTDOWN; break; } ioSession.setEvent(SelectionKey.OP_WRITE); } void doStaleCheck(final Consumer callback) throws IOException { callback.accept(ioSession.isOpen() && connState.compareTo(ConnectionState.ACTIVE) == 0); } void commitMessageHead( final OutgoingMessage messageHead, final boolean endStream, final FlushMode flushMode) throws HttpException, IOException { ioSession.getLock().lock(); try { outgoingMessageWriter.write(messageHead, outbuf); updateOutputMetrics(messageHead, connMetrics); if (!endStream) { final ContentEncoder contentEncoder; if (handleOutgoingMessage(messageHead)) { final long len = outgoingContentStrategy.determineLength(messageHead); contentEncoder = createContentEncoder(len, ioSession, outbuf, outTransportMetrics); } else { contentEncoder = null; } if (contentEncoder != null) { outgoingMessage = new Message<>(messageHead, contentEncoder); } } outgoingMessageWriter.reset(); if (flushMode == FlushMode.IMMEDIATE) { final int bytesWritten = outbuf.flush(ioSession); if (bytesWritten > 0) { outTransportMetrics.incrementBytesTransferred(bytesWritten); } } ioSession.setEvent(EventMask.WRITE); } finally { ioSession.getLock().unlock(); } } void requestSessionInput() { ioSession.setEvent(SelectionKey.OP_READ); } void requestSessionOutput() { outputRequests.incrementAndGet(); ioSession.setEvent(SelectionKey.OP_WRITE); } Timeout getSessionTimeout() { return ioSession.getSocketTimeout(); } void setSessionTimeout(final Timeout timeout) { ioSession.setSocketTimeout(timeout); } void suspendSessionInput() { ioSession.clearEvent(SelectionKey.OP_READ); } void suspendSessionOutput() throws IOException { ioSession.getLock().lock(); try { if (outbuf.hasData()) { final int bytesWritten = outbuf.flush(ioSession); if (bytesWritten > 0) { outTransportMetrics.incrementBytesTransferred(bytesWritten); } } else { ioSession.clearEvent(SelectionKey.OP_WRITE); } } finally { ioSession.getLock().unlock(); } } int streamOutput(final ByteBuffer src) throws IOException { ioSession.getLock().lock(); try { if (outgoingMessage == null) { throw new ConnectionClosedException(); } final ContentEncoder contentEncoder = outgoingMessage.getBody(); final int bytesWritten = contentEncoder.write(src); if (bytesWritten > 0) { ioSession.setEvent(SelectionKey.OP_WRITE); } return bytesWritten; } finally { ioSession.getLock().unlock(); } } enum MessageDelineation { NONE, CHUNK_CODED, MESSAGE_HEAD } MessageDelineation endOutputStream(final List trailers) throws IOException { ioSession.getLock().lock(); try { if (outgoingMessage == null) { return MessageDelineation.NONE; } final ContentEncoder contentEncoder = outgoingMessage.getBody(); contentEncoder.complete(trailers); ioSession.setEvent(SelectionKey.OP_WRITE); outgoingMessage = null; return contentEncoder instanceof ChunkEncoder ? MessageDelineation.CHUNK_CODED : MessageDelineation.MESSAGE_HEAD; } finally { ioSession.getLock().unlock(); } } boolean isOutputCompleted() { ioSession.getLock().lock(); try { if (outgoingMessage == null) { return true; } final ContentEncoder contentEncoder = outgoingMessage.getBody(); return contentEncoder.isCompleted(); } finally { ioSession.getLock().unlock(); } } @Override public void close() throws IOException { ioSession.enqueue(ShutdownCommand.GRACEFUL, Command.Priority.NORMAL); } @Override public void close(final CloseMode closeMode) { ioSession.enqueue(new ShutdownCommand(closeMode), Command.Priority.IMMEDIATE); } @Override public boolean isOpen() { return connState.compareTo(ConnectionState.ACTIVE) <= 0; } @Override public Timeout getSocketTimeout() { return ioSession.getSocketTimeout(); } @Override public void setSocketTimeout(final Timeout timeout) { ioSession.setSocketTimeout(timeout); } @Override public EndpointDetails getEndpointDetails() { if (endpointDetails == null) { endpointDetails = new BasicEndpointDetails( ioSession.getRemoteAddress(), ioSession.getLocalAddress(), connMetrics, ioSession.getSocketTimeout()); } return endpointDetails; } @Override public ProtocolVersion getProtocolVersion() { return version; } @Override public SocketAddress getRemoteAddress() { return ioSession.getRemoteAddress(); } @Override public SocketAddress getLocalAddress() { return ioSession.getLocalAddress(); } @Override public SSLSession getSSLSession() { final TlsDetails tlsDetails = ioSession.getTlsDetails(); return tlsDetails != null ? tlsDetails.getSSLSession() : null; } void appendState(final StringBuilder buf) { buf.append("connState=").append(connState) .append(", inbuf=").append(inbuf) .append(", outbuf=").append(outbuf) .append(", inputWindow=").append(capacityWindow != null ? capacityWindow.getWindow() : 0); } static class CapacityWindow implements CapacityChannel { private final IOSession ioSession; private final ReentrantLock lock; private int window; private boolean closed; CapacityWindow(final int window, final IOSession ioSession) { this.window = window; this.ioSession = ioSession; this.lock = new ReentrantLock(); } @Override public void update(final int increment) throws IOException { lock.lock(); try { if (closed) { return; } if (increment > 0) { updateWindow(increment); ioSession.setEvent(SelectionKey.OP_READ); } } finally { lock.unlock(); } } /** * Internal method for removing capacity. We don't need to check * if this channel is closed in it. */ int removeCapacity(final int delta) { lock.lock(); try { updateWindow(-delta); if (window <= 0) { ioSession.clearEvent(SelectionKey.OP_READ); } return window; } finally { lock.unlock(); } } private void updateWindow(final int delta) { int newValue = window + delta; // Math.addExact if (((window ^ newValue) & (delta ^ newValue)) < 0) { newValue = delta < 0 ? Integer.MIN_VALUE : Integer.MAX_VALUE; } window = newValue; } /** * Closes the capacity channel, preventing user code from accidentally requesting * read events outside of the context of the request the channel was created for */ void close() { lock.lock(); try { closed = true; } finally { lock.unlock(); } } // visible for testing int getWindow() { return window; } } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractMessageParser.java0100644 0000000 0000000 00000017115 00000250600 027355 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpMessage; import org.apache.hc.core5.http.MessageConstraintException; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.message.LazyLineParser; import org.apache.hc.core5.http.message.LineParser; import org.apache.hc.core5.http.nio.NHttpMessageParser; import org.apache.hc.core5.http.nio.SessionInputBuffer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; /** * Abstract {@link NHttpMessageParser} that serves as a base for all message * parser implementations. * * @param The type of {@link HttpMessage}. * @since 4.0 */ public abstract class AbstractMessageParser implements NHttpMessageParser { private enum State { READ_HEAD_LINE, READ_HEADERS, COMPLETED } private final Http1Config http1Config; private final LineParser lineParser; private State state; private T message; private CharArrayBuffer lineBuf; private final List headerBufs; private int emptyLineCount; /** * Constructs a new instance for a subclass. * * @param http1Config HTTP/1.1 protocol parameters. * @param lineParser How to parse lines in an HTTP message. * @since 5.3 */ public AbstractMessageParser(final Http1Config http1Config, final LineParser lineParser) { super(); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.lineParser = lineParser != null ? lineParser : LazyLineParser.INSTANCE; this.headerBufs = new ArrayList<>(); this.state = State.READ_HEAD_LINE; } /** * Constructs a new instance for a subclass. * * @param lineParser How to parse lines in an HTTP message. * @param messageConstraints HTTP/1.1 protocol parameters. * @deprecated Use {@link #AbstractMessageParser(Http1Config, LineParser)} */ @Deprecated public AbstractMessageParser(final LineParser lineParser, final Http1Config messageConstraints) { this(messageConstraints, lineParser); } LineParser getLineParser() { return this.lineParser; } @Override public void reset() { this.state = State.READ_HEAD_LINE; this.headerBufs.clear(); this.emptyLineCount = 0; this.message = null; } /** * Creates {@link HttpMessage} instance based on the content of the input * buffer containing the first line of the incoming HTTP message. * * @param buffer the line buffer. * @return HTTP message. * @throws HttpException in case of HTTP protocol violation */ protected abstract T createMessage(CharArrayBuffer buffer) throws HttpException; private T parseHeadLine() throws IOException, HttpException { if (this.lineBuf.isEmpty()) { this.emptyLineCount++; if (this.emptyLineCount >= this.http1Config.getMaxEmptyLineCount()) { throw new MessageConstraintException("Maximum empty line limit exceeded"); } return null; } return createMessage(this.lineBuf); } private void parseHeader() throws IOException { final CharArrayBuffer current = this.lineBuf; final int count = this.headerBufs.size(); if ((this.lineBuf.charAt(0) == ' ' || this.lineBuf.charAt(0) == '\t') && count > 0) { // Handle folded header line final CharArrayBuffer previous = this.headerBufs.get(count - 1); int i = 0; while (i < current.length()) { final char ch = current.charAt(i); if (ch != ' ' && ch != '\t') { break; } i++; } final int maxLineLen = this.http1Config.getMaxLineLength(); if (maxLineLen > 0 && previous.length() + 1 + current.length() - i > maxLineLen) { throw new MessageConstraintException("Maximum line length limit exceeded"); } previous.append(' '); previous.append(current, i, current.length() - i); } else { this.headerBufs.add(current); this.lineBuf = null; } } @Override public T parse( final SessionInputBuffer sessionBuffer, final boolean endOfStream) throws IOException, HttpException { Args.notNull(sessionBuffer, "Session input buffer"); while (this.state != State.COMPLETED) { if (this.lineBuf == null) { this.lineBuf = new CharArrayBuffer(64); } else { this.lineBuf.clear(); } final boolean lineComplete = sessionBuffer.readLine(this.lineBuf, endOfStream); final int maxLineLen = this.http1Config.getMaxLineLength(); if (maxLineLen > 0 && (this.lineBuf.length() > maxLineLen || !lineComplete && sessionBuffer.length() > maxLineLen)) { throw new MessageConstraintException("Maximum line length limit exceeded"); } if (!lineComplete) { break; } switch (this.state) { case READ_HEAD_LINE: this.message = parseHeadLine(); if (this.message != null) { this.state = State.READ_HEADERS; } break; case READ_HEADERS: if (this.lineBuf.length() > 0) { final int maxHeaderCount = this.http1Config.getMaxHeaderCount(); if (maxHeaderCount > 0 && headerBufs.size() >= maxHeaderCount) { throw new MessageConstraintException("Maximum header count exceeded"); } parseHeader(); } else { this.state = State.COMPLETED; } break; } if (endOfStream && !sessionBuffer.hasData()) { this.state = State.COMPLETED; } } if (this.state == State.COMPLETED) { for (final CharArrayBuffer buffer : this.headerBufs) { this.message.addHeader(this.lineParser.parseHeader(buffer)); } return this.message; } return null; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractMessageWriter.java0100644 0000000 0000000 00000007546 00000250600 027404 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.util.Iterator; import org.apache.hc.core5.http.FormattedHeader; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpMessage; import org.apache.hc.core5.http.message.BasicLineFormatter; import org.apache.hc.core5.http.message.LineFormatter; import org.apache.hc.core5.http.nio.NHttpMessageWriter; import org.apache.hc.core5.http.nio.SessionOutputBuffer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; /** * Abstract {@link NHttpMessageWriter} that serves as a base for all message * writer implementations. * * @param The type of {@link HttpMessage}. * @since 4.0 */ public abstract class AbstractMessageWriter implements NHttpMessageWriter { private final CharArrayBuffer lineBuf; private final LineFormatter lineFormatter; /** * Creates an instance of AbstractMessageWriter. * * @param formatter the line formatter If {@code null} {@link BasicLineFormatter#INSTANCE} * will be used. * * @since 4.3 */ public AbstractMessageWriter(final LineFormatter formatter) { super(); this.lineFormatter = (formatter != null) ? formatter : BasicLineFormatter.INSTANCE; this.lineBuf = new CharArrayBuffer(64); } LineFormatter getLineFormatter() { return this.lineFormatter; } @Override public void reset() { } /** * Writes out the first line of {@link HttpMessage}. * * @param message HTTP message. * @throws IOException in case of an I/O error. */ protected abstract void writeHeadLine(T message, CharArrayBuffer buffer) throws IOException; @Override public void write(final T message, final SessionOutputBuffer sessionBuffer) throws IOException, HttpException { Args.notNull(message, "HTTP message"); Args.notNull(sessionBuffer, "Session output buffer"); writeHeadLine(message, this.lineBuf); sessionBuffer.writeLine(this.lineBuf); for (final Iterator

it = message.headerIterator(); it.hasNext(); ) { final Header header = it.next(); if (header instanceof FormattedHeader) { final CharArrayBuffer buffer = ((FormattedHeader) header).getBuffer(); sessionBuffer.writeLine(buffer); } else { this.lineBuf.clear(); this.lineFormatter.formatHeader(this.lineBuf, header); sessionBuffer.writeLine(this.lineBuf); } } this.lineBuf.clear(); sessionBuffer.writeLine(this.lineBuf); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/BufferedData.java0100644 0000000 0000000 00000005755 00000250600 025453 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import org.apache.hc.core5.util.Args; /** * A buffer that expand its capacity on demand. Internally, this class is backed * by an instance of {@link ByteBuffer}. *

* This class is not thread safe. *

* @since 5.0 */ public class BufferedData extends ExpandableBuffer { public static BufferedData allocate(final int bufferSize) { return new BufferedData(bufferSize); } protected BufferedData(final int bufferSize) { super(bufferSize); } @Override public final boolean hasData() { return super.hasData(); } @Override public final int length() { return super.length(); } @Override public final int capacity() { return super.capacity(); } @Override public final void clear() { super.clear(); } public final void put(final ByteBuffer src) { Args.notNull(src, "Data source"); setInputMode(); final int requiredCapacity = buffer().position() + src.remaining(); ensureAdjustedCapacity(requiredCapacity); buffer().put(src); } public final int readFrom(final ReadableByteChannel channel) throws IOException { Args.notNull(channel, "Channel"); setInputMode(); if (!buffer().hasRemaining()) { expand(); } return channel.read(buffer()); } public final int writeTo(final WritableByteChannel dst) throws IOException { if (dst == null) { return 0; } setOutputMode(); return dst.write(buffer()); } public final ByteBuffer data() { setOutputMode(); return buffer(); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ChunkDecoder.java0100644 0000000 0000000 00000024716 00000250600 025473 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.util.ArrayList; import java.util.List; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.MalformedChunkCodingException; import org.apache.hc.core5.http.MessageConstraintException; import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.http.TruncatedChunkException; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.message.BufferedHeader; import org.apache.hc.core5.http.nio.SessionInputBuffer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; /** * Implements chunked transfer decoding. The content is received in small chunks. * Entities transferred using this encoder can be of unlimited length. * * @since 4.0 */ public class ChunkDecoder extends AbstractContentDecoder { private enum State { READ_CONTENT, READ_FOOTERS, COMPLETED } private State state; private boolean endOfChunk; private boolean endOfStream; private CharArrayBuffer lineBuf; private long chunkSize; private long pos; private final Http1Config http1Config; private final List trailerBufs; private final List
trailers; /** * @since 4.4 */ public ChunkDecoder( final ReadableByteChannel channel, final SessionInputBuffer buffer, final Http1Config http1Config, final BasicHttpTransportMetrics metrics) { super(channel, buffer, metrics); this.state = State.READ_CONTENT; this.chunkSize = -1L; this.pos = 0L; this.endOfChunk = false; this.endOfStream = false; this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.trailerBufs = new ArrayList<>(); this.trailers = new ArrayList<>(); } public ChunkDecoder( final ReadableByteChannel channel, final SessionInputBuffer buffer, final BasicHttpTransportMetrics metrics) { this(channel, buffer, null, metrics); } private void readChunkHead() throws IOException { if (this.lineBuf == null) { this.lineBuf = new CharArrayBuffer(32); } else { this.lineBuf.clear(); } if (this.endOfChunk) { if (this.buffer.readLine(this.lineBuf, this.endOfStream)) { if (!this.lineBuf.isEmpty()) { throw new MalformedChunkCodingException("CRLF expected at end of chunk"); } } else { if (this.buffer.length() > 2 || this.endOfStream) { throw new MalformedChunkCodingException("CRLF expected at end of chunk"); } return; } this.endOfChunk = false; } final boolean lineComplete = this.buffer.readLine(this.lineBuf, this.endOfStream); final int maxLineLen = this.http1Config.getMaxLineLength(); if (maxLineLen > 0 && (this.lineBuf.length() > maxLineLen || !lineComplete && this.buffer.length() > maxLineLen)) { throw new MessageConstraintException("Maximum line length limit exceeded"); } if (lineComplete) { int separator = this.lineBuf.indexOf(';'); if (separator < 0) { separator = this.lineBuf.length(); } final String s = this.lineBuf.substringTrimmed(0, separator); try { this.chunkSize = Long.parseLong(s, 16); } catch (final NumberFormatException e) { throw new MalformedChunkCodingException("Bad chunk header: " + s); } this.pos = 0L; } else if (this.endOfStream) { throw new ConnectionClosedException( "Premature end of chunk coded message body: closing chunk expected"); } } private void parseHeader() throws IOException { final CharArrayBuffer current = this.lineBuf; final int count = this.trailerBufs.size(); if ((this.lineBuf.charAt(0) == ' ' || this.lineBuf.charAt(0) == '\t') && count > 0) { // Handle folded header line final CharArrayBuffer previous = this.trailerBufs.get(count - 1); int i = 0; while (i < current.length()) { final char ch = current.charAt(i); if (ch != ' ' && ch != '\t') { break; } i++; } final int maxLineLen = this.http1Config.getMaxLineLength(); if (maxLineLen > 0 && previous.length() + 1 + current.length() - i > maxLineLen) { throw new MessageConstraintException("Maximum line length limit exceeded"); } previous.append(' '); previous.append(current, i, current.length() - i); } else { this.trailerBufs.add(current); this.lineBuf = null; } } private void processFooters() throws IOException { final int count = this.trailerBufs.size(); if (count > 0) { this.trailers.clear(); for (int i = 0; i < this.trailerBufs.size(); i++) { try { this.trailers.add(new BufferedHeader(this.trailerBufs.get(i))); } catch (final ParseException ex) { throw new IOException(ex); } } } this.trailerBufs.clear(); } @Override public int read(final ByteBuffer dst) throws IOException { Args.notNull(dst, "Byte buffer"); if (this.state == State.COMPLETED) { return -1; } int totalRead = 0; while (this.state != State.COMPLETED) { if (!this.buffer.hasData() || this.chunkSize == -1L) { final int bytesRead = fillBufferFromChannel(); if (bytesRead == -1) { this.endOfStream = true; } } switch (this.state) { case READ_CONTENT: if (this.chunkSize == -1L) { readChunkHead(); if (this.chunkSize == -1L) { // Unable to read a chunk head return totalRead; } if (this.chunkSize == 0L) { // Last chunk. Read footers this.chunkSize = -1L; this.state = State.READ_FOOTERS; break; } } final long maxLen = this.chunkSize - this.pos; final int len = this.buffer.read(dst, (int) Math.min(maxLen, Integer.MAX_VALUE)); if (len > 0) { this.pos += len; totalRead += len; } else { if (!this.buffer.hasData() && this.endOfStream) { this.state = State.COMPLETED; setCompleted(); throw new TruncatedChunkException( "Truncated chunk (expected size: %d; actual size: %d)", chunkSize, pos); } } if (this.pos == this.chunkSize) { // At the end of the chunk this.chunkSize = -1L; this.pos = 0L; this.endOfChunk = true; break; } return totalRead; case READ_FOOTERS: if (this.lineBuf == null) { this.lineBuf = new CharArrayBuffer(32); } else { this.lineBuf.clear(); } if (!this.buffer.readLine(this.lineBuf, this.endOfStream)) { // Unable to read a footer if (this.endOfStream) { this.state = State.COMPLETED; setCompleted(); } return totalRead; } if (this.lineBuf.length() > 0) { final int maxHeaderCount = this.http1Config.getMaxHeaderCount(); if (maxHeaderCount > 0 && trailerBufs.size() >= maxHeaderCount) { throw new MessageConstraintException("Maximum header count exceeded"); } parseHeader(); } else { this.state = State.COMPLETED; setCompleted(); processFooters(); } break; } } return totalRead; } @Override public List getTrailers() { return this.trailers.isEmpty() ? null : new ArrayList<>(this.trailers); } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("[chunk-coded; completed: "); sb.append(this.completed); sb.append("]"); return sb.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ChunkEncoder.java0100644 0000000 0000000 00000013270 00000250600 025476 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.util.List; import org.apache.hc.core5.http.FormattedHeader; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.message.BasicLineFormatter; import org.apache.hc.core5.http.nio.SessionOutputBuffer; import org.apache.hc.core5.util.CharArrayBuffer; /** * Implements chunked transfer coding. The content is sent in small chunks. * Entities transferred using this decoder can be of unlimited length. * * @since 4.0 */ public class ChunkEncoder extends AbstractContentEncoder { private final int chunkSizeHint; private final CharArrayBuffer lineBuffer; /** * @param channel underlying channel. * @param buffer session buffer. * @param metrics transport metrics. * * @since 5.0 */ public ChunkEncoder( final WritableByteChannel channel, final SessionOutputBuffer buffer, final BasicHttpTransportMetrics metrics, final int chunkSizeHint) { super(channel, buffer, metrics); this.chunkSizeHint = Math.max(chunkSizeHint, 0); this.lineBuffer = new CharArrayBuffer(16); } public ChunkEncoder( final WritableByteChannel channel, final SessionOutputBuffer buffer, final BasicHttpTransportMetrics metrics) { this(channel, buffer, metrics, 0); } @Override public int write(final ByteBuffer src) throws IOException { if (src == null) { return 0; } assertNotCompleted(); int total = 0; while (src.hasRemaining()) { int chunk = src.remaining(); int avail; avail = this.buffer.capacity(); // subtract the length of the longest chunk header // 12345678\r\n // \r\n avail -= 12; if (avail > 0) { if (avail < chunk) { // write no more than 'avail' bytes chunk = avail; this.lineBuffer.clear(); this.lineBuffer.append(Integer.toHexString(chunk)); this.buffer.writeLine(this.lineBuffer); final int oldlimit = src.limit(); src.limit(src.position() + chunk); this.buffer.write(src); src.limit(oldlimit); } else { // write all this.lineBuffer.clear(); this.lineBuffer.append(Integer.toHexString(chunk)); this.buffer.writeLine(this.lineBuffer); this.buffer.write(src); } this.lineBuffer.clear(); this.buffer.writeLine(this.lineBuffer); total += chunk; } if (this.buffer.length() >= this.chunkSizeHint || src.hasRemaining()) { final int bytesWritten = flushToChannel(); if (bytesWritten == 0) { break; } } } return total; } @Override public void complete(final List trailers) throws IOException { assertNotCompleted(); this.lineBuffer.clear(); this.lineBuffer.append("0"); this.buffer.writeLine(this.lineBuffer); writeTrailers(trailers); this.lineBuffer.clear(); this.buffer.writeLine(this.lineBuffer); super.complete(trailers); } private void writeTrailers(final List trailers) throws IOException { if (trailers != null) { for (int i = 0; i < trailers.size(); i++) { final Header header = trailers.get(i); if (header instanceof FormattedHeader) { final CharArrayBuffer chbuffer = ((FormattedHeader) header).getBuffer(); buffer.writeLine(chbuffer); } else { this.lineBuffer.clear(); BasicLineFormatter.INSTANCE.formatHeader(this.lineBuffer, header); buffer.writeLine(this.lineBuffer); } } } } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("[chunk-coded; completed: "); sb.append(isCompleted()); sb.append("]"); return sb.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1IOEventHandler.java0100644 0000000 0000000 00000004553 00000250600 030021 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.net.InetAddressUtils; /** * {@link org.apache.hc.core5.reactor.IOEventHandler} that implements * client side HTTP/1.1 messaging protocol with full support for * duplexed message transmission and message pipelining. * * @since 5.0 */ public class ClientHttp1IOEventHandler extends AbstractHttp1IOEventHandler { public ClientHttp1IOEventHandler(final ClientHttp1StreamDuplexer streamDuplexer) { super(streamDuplexer); } @Override public String toString() { final StringBuilder buf = new StringBuilder(); InetAddressUtils.formatAddress(buf, getLocalAddress()); buf.append("->"); InetAddressUtils.formatAddress(buf, getRemoteAddress()); buf.append(" ["); streamDuplexer.appendState(buf); buf.append("]"); return buf.toString(); } @Override public ProtocolVersion getProtocolVersion() { final ProtocolVersion protocolVersion = super.getProtocolVersion(); return protocolVersion != null ? protocolVersion : HttpVersion.HTTP_1_1; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1IOEventHandlerFactory.java0100644 0000000 0000000 00000005776 00000250600 031361 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.reactor.EndpointParameters; import org.apache.hc.core5.reactor.IOEventHandler; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; /** * {@link ClientHttp1IOEventHandler} factory. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class ClientHttp1IOEventHandlerFactory implements IOEventHandlerFactory { private final ClientHttp1StreamDuplexerFactory streamDuplexerFactory; private final TlsStrategy tlsStrategy; private final Timeout handshakeTimeout; public ClientHttp1IOEventHandlerFactory( final ClientHttp1StreamDuplexerFactory streamDuplexerFactory, final TlsStrategy tlsStrategy, final Timeout handshakeTimeout) { this.streamDuplexerFactory = Args.notNull(streamDuplexerFactory, "Stream duplexer factory"); this.tlsStrategy = tlsStrategy; this.handshakeTimeout = handshakeTimeout; } @Override public IOEventHandler createHandler(final ProtocolIOSession ioSession, final Object attachment) { if (attachment instanceof EndpointParameters) { final EndpointParameters params = (EndpointParameters) attachment; if (tlsStrategy != null && URIScheme.HTTPS.same(params.getScheme())) { tlsStrategy.upgrade(ioSession, params, params.getAttachment(), handshakeTimeout, null); } } return new ClientHttp1IOEventHandler(streamDuplexerFactory.create(ioSession)); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamDuplexer.java0100644 0000000 0000000 00000035233 00000250600 030155 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.LengthRequiredException; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.BasicHttpConnectionMetrics; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.ContentDecoder; import org.apache.hc.core5.http.nio.ContentEncoder; import org.apache.hc.core5.http.nio.NHttpMessageParser; import org.apache.hc.core5.http.nio.NHttpMessageWriter; import org.apache.hc.core5.http.nio.SessionInputBuffer; import org.apache.hc.core5.http.nio.SessionOutputBuffer; import org.apache.hc.core5.http.nio.command.RequestExecutionCommand; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; import org.apache.hc.core5.util.Timeout; /** * I/O event handler for events fired by {@link ProtocolIOSession} that implements * client side HTTP/1.1 messaging protocol with full support for * duplexed message transmission and message pipelining. * * @since 5.0 */ @Internal public class ClientHttp1StreamDuplexer extends AbstractHttp1StreamDuplexer { private final HttpProcessor httpProcessor; private final ConnectionReuseStrategy connectionReuseStrategy; private final Http1Config http1Config; private final Http1StreamListener streamListener; private final Queue pipeline; private final Http1StreamChannel outputChannel; private volatile ClientHttp1StreamHandler outgoing; private volatile ClientHttp1StreamHandler incoming; public ClientHttp1StreamDuplexer( final ProtocolIOSession ioSession, final HttpProcessor httpProcessor, final Http1Config http1Config, final CharCodingConfig charCodingConfig, final ConnectionReuseStrategy connectionReuseStrategy, final NHttpMessageParser incomingMessageParser, final NHttpMessageWriter outgoingMessageWriter, final ContentLengthStrategy incomingContentStrategy, final ContentLengthStrategy outgoingContentStrategy, final Http1StreamListener streamListener) { super(ioSession, http1Config, charCodingConfig, incomingMessageParser, outgoingMessageWriter, incomingContentStrategy, outgoingContentStrategy); this.httpProcessor = Args.notNull(httpProcessor, "HTTP processor"); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.connectionReuseStrategy = connectionReuseStrategy != null ? connectionReuseStrategy : DefaultConnectionReuseStrategy.INSTANCE; this.streamListener = streamListener; this.pipeline = new ConcurrentLinkedQueue<>(); this.outputChannel = new Http1StreamChannel() { @Override public void close() { shutdownSession(CloseMode.IMMEDIATE); } @Override public void close(final CloseMode closeMode) { shutdownSession(closeMode); } @Override public void submit( final HttpRequest request, final boolean endStream, final FlushMode flushMode) throws HttpException, IOException { if (streamListener != null) { streamListener.onRequestHead(ClientHttp1StreamDuplexer.this, request); } commitMessageHead(request, endStream, flushMode); } @Override public void suspendOutput() throws IOException { suspendSessionOutput(); } @Override public void requestOutput() { requestSessionOutput(); } @Override public Timeout getSocketTimeout() { return getSessionTimeout(); } @Override public void setSocketTimeout(final Timeout timeout) { setSessionTimeout(timeout); } @Override public int write(final ByteBuffer src) throws IOException { return streamOutput(src); } @Override public void complete(final List trailers) throws IOException { endOutputStream(trailers); } @Override public boolean isCompleted() { return isOutputCompleted(); } @Override public boolean abortGracefully() throws IOException { final MessageDelineation messageDelineation = endOutputStream(null); return messageDelineation != MessageDelineation.MESSAGE_HEAD; } @Override public void activate() throws HttpException, IOException { } }; } @Override void terminate(final Exception exception) { if (incoming != null) { incoming.failed(exception); incoming.releaseResources(); incoming = null; } if (outgoing != null) { outgoing.failed(exception); outgoing.releaseResources(); outgoing = null; } for (;;) { final ClientHttp1StreamHandler handler = pipeline.poll(); if (handler != null) { handler.failed(exception); handler.releaseResources(); } else { break; } } } @Override void disconnected() { if (incoming != null) { if (!incoming.isCompleted()) { incoming.failed(new ConnectionClosedException()); } incoming.releaseResources(); incoming = null; } if (outgoing != null) { if (!outgoing.isCompleted()) { outgoing.failed(new ConnectionClosedException()); } outgoing.releaseResources(); outgoing = null; } for (;;) { final ClientHttp1StreamHandler handler = pipeline.poll(); if (handler != null) { handler.failed(new ConnectionClosedException()); handler.releaseResources(); } else { break; } } } @Override void updateInputMetrics(final HttpResponse response, final BasicHttpConnectionMetrics connMetrics) { if (response.getCode() >= HttpStatus.SC_OK) { connMetrics.incrementRequestCount(); } } @Override void updateOutputMetrics(final HttpRequest request, final BasicHttpConnectionMetrics connMetrics) { connMetrics.incrementRequestCount(); } @Override protected boolean handleIncomingMessage(final HttpResponse response) throws HttpException { if (incoming == null) { incoming = pipeline.poll(); } if (incoming == null) { throw new HttpException("Unexpected response"); } return MessageSupport.canResponseHaveBody(incoming.getRequestMethod(), response); } @Override protected ContentDecoder createContentDecoder( final long len, final ReadableByteChannel channel, final SessionInputBuffer buffer, final BasicHttpTransportMetrics metrics) throws HttpException { if (len >= 0) { return new LengthDelimitedDecoder(channel, buffer, metrics, len); } else if (len == ContentLengthStrategy.CHUNKED) { return new ChunkDecoder(channel, buffer, http1Config, metrics); } else { return new IdentityDecoder(channel, buffer, metrics); } } @Override protected boolean handleOutgoingMessage(final HttpRequest request) throws HttpException { return true; } @Override protected ContentEncoder createContentEncoder( final long len, final WritableByteChannel channel, final SessionOutputBuffer buffer, final BasicHttpTransportMetrics metrics) throws HttpException { final int chunkSizeHint = http1Config.getChunkSizeHint() >= 0 ? http1Config.getChunkSizeHint() : 2048; if (len >= 0) { return new LengthDelimitedEncoder(channel, buffer, metrics, len, chunkSizeHint); } else if (len == ContentLengthStrategy.CHUNKED) { return new ChunkEncoder(channel, buffer, metrics, chunkSizeHint); } else { throw new LengthRequiredException(); } } @Override boolean isRequestInitiated() { return outgoing != null && !outgoing.isRequestFinal(); } @Override boolean inputIdle() { return incoming == null && pipeline.isEmpty(); } @Override boolean outputIdle() { return outgoing == null; } @Override void outputEnd() throws HttpException, IOException { if (outgoing != null) { if (outgoing.isCompleted()) { outgoing.releaseResources(); } outgoing = null; } } @Override void execute(final RequestExecutionCommand executionCommand) throws HttpException, IOException { final AsyncClientExchangeHandler exchangeHandler = executionCommand.getExchangeHandler(); final HttpCoreContext context = HttpCoreContext.castOrCreate(executionCommand.getContext()); context.setSSLSession(getSSLSession()); context.setEndpointDetails(getEndpointDetails()); final ClientHttp1StreamHandler handler = new ClientHttp1StreamHandler( outputChannel, httpProcessor, http1Config, connectionReuseStrategy, exchangeHandler, context); pipeline.add(handler); outgoing = handler; if (handler.isOutputReady()) { handler.produceOutput(); } } @Override boolean isOutputReady() { return outgoing != null && outgoing.isOutputReady(); } @Override void produceOutput() throws HttpException, IOException { if (outgoing != null) { outgoing.produceOutput(); } } @Override void consumeHeader(final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException { if (streamListener != null) { streamListener.onResponseHead(this, response); } Asserts.notNull(incoming, "Response stream handler"); incoming.consumeHeader(response, entityDetails); } @Override void consumeData(final ByteBuffer src) throws HttpException, IOException { Asserts.notNull(incoming, "Response stream handler"); incoming.consumeData(src); } @Override void updateCapacity(final CapacityChannel capacityChannel) throws HttpException, IOException { Asserts.notNull(incoming, "Response stream handler"); incoming.updateCapacity(capacityChannel); } @Override void dataEnd(final List trailers) throws HttpException, IOException { Asserts.notNull(incoming, "Response stream handler"); incoming.dataEnd(trailers); } @Override void inputEnd() throws HttpException, IOException { if (incoming != null && incoming.isResponseFinal()) { if (streamListener != null) { streamListener.onExchangeComplete(this, isOpen()); } if (incoming.isCompleted()) { incoming.releaseResources(); } incoming = null; } } @Override boolean handleTimeout() { return outgoing != null && outgoing.handleTimeout(); } @Override void appendState(final StringBuilder buf) { super.appendState(buf); buf.append(", incoming=["); if (incoming != null) { incoming.appendState(buf); } buf.append("], outgoing=["); if (outgoing != null) { outgoing.appendState(buf); } buf.append("], pipeline="); buf.append(pipeline.size()); } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append("["); appendState(buf); buf.append("]"); return buf.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamDuplexerFactory.java0100644 0000000 0000000 00000014371 00000250600 031505 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.nio.NHttpMessageParserFactory; import org.apache.hc.core5.http.nio.NHttpMessageWriterFactory; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.util.Args; /** * {@link ClientHttp1StreamDuplexer} factory. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) @Internal public final class ClientHttp1StreamDuplexerFactory { private final HttpProcessor httpProcessor; private final Http1Config http1Config; private final CharCodingConfig charCodingConfig; private final ConnectionReuseStrategy connectionReuseStrategy; private final NHttpMessageParserFactory responseParserFactory; private final NHttpMessageWriterFactory requestWriterFactory; private final ContentLengthStrategy incomingContentStrategy; private final ContentLengthStrategy outgoingContentStrategy; private final Http1StreamListener streamListener; public ClientHttp1StreamDuplexerFactory( final HttpProcessor httpProcessor, final Http1Config http1Config, final CharCodingConfig charCodingConfig, final ConnectionReuseStrategy connectionReuseStrategy, final NHttpMessageParserFactory responseParserFactory, final NHttpMessageWriterFactory requestWriterFactory, final ContentLengthStrategy incomingContentStrategy, final ContentLengthStrategy outgoingContentStrategy, final Http1StreamListener streamListener) { this.httpProcessor = Args.notNull(httpProcessor, "HTTP processor"); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.charCodingConfig = charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT; this.connectionReuseStrategy = connectionReuseStrategy != null ? connectionReuseStrategy : DefaultConnectionReuseStrategy.INSTANCE; this.responseParserFactory = responseParserFactory != null ? responseParserFactory : new DefaultHttpResponseParserFactory(this.http1Config); this.requestWriterFactory = requestWriterFactory != null ? requestWriterFactory : new DefaultHttpRequestWriterFactory(this.http1Config); this.incomingContentStrategy = incomingContentStrategy != null ? incomingContentStrategy : DefaultContentLengthStrategy.INSTANCE; this.outgoingContentStrategy = outgoingContentStrategy != null ? outgoingContentStrategy : DefaultContentLengthStrategy.INSTANCE; this.streamListener = streamListener; } public ClientHttp1StreamDuplexerFactory( final HttpProcessor httpProcessor, final Http1Config http1Config, final CharCodingConfig charCodingConfig, final ConnectionReuseStrategy connectionReuseStrategy, final NHttpMessageParserFactory responseParserFactory, final NHttpMessageWriterFactory requestWriterFactory, final Http1StreamListener streamListener) { this(httpProcessor, http1Config, charCodingConfig, connectionReuseStrategy, responseParserFactory, requestWriterFactory, null, null, streamListener); } public ClientHttp1StreamDuplexerFactory( final HttpProcessor httpProcessor, final Http1Config http1Config, final CharCodingConfig charCodingConfig, final Http1StreamListener streamListener) { this(httpProcessor, http1Config, charCodingConfig, null, null, null, streamListener); } public ClientHttp1StreamDuplexerFactory( final HttpProcessor httpProcessor, final Http1Config http1Config, final CharCodingConfig charCodingConfig) { this(httpProcessor, http1Config, charCodingConfig, null); } public ClientHttp1StreamDuplexer create(final ProtocolIOSession ioSession) { return new ClientHttp1StreamDuplexer( ioSession, httpProcessor, http1Config, charCodingConfig, connectionReuseStrategy, responseParserFactory.create(), requestWriterFactory.create(), incomingContentStrategy, outgoingContentStrategy, streamListener); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamHandler.java0100644 0000000 0000000 00000031656 00000250600 027747 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.UnsupportedHttpVersionException; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.ResourceHolder; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.util.Timeout; class ClientHttp1StreamHandler implements ResourceHolder { public static final Timeout DEFAULT_WAIT_FOR_CONTINUE = Timeout.ofSeconds(3); private final Http1StreamChannel outputChannel; private final DataStreamChannel internalDataChannel; private final HttpProcessor httpProcessor; private final Http1Config http1Config; private final ConnectionReuseStrategy connectionReuseStrategy; private final AsyncClientExchangeHandler exchangeHandler; private final HttpCoreContext context; private final AtomicReference requestState; private final AtomicReference responseState; private final AtomicBoolean requestCommitted; private final AtomicBoolean done; private volatile boolean keepAlive; private volatile Timeout timeout; private volatile HttpRequest committedRequest; ClientHttp1StreamHandler( final Http1StreamChannel outputChannel, final HttpProcessor httpProcessor, final Http1Config http1Config, final ConnectionReuseStrategy connectionReuseStrategy, final AsyncClientExchangeHandler exchangeHandler, final HttpCoreContext context) { this.outputChannel = outputChannel; this.internalDataChannel = new DataStreamChannel() { @Override public void requestOutput() { outputChannel.requestOutput(); } @Override public void endStream(final List trailers) throws IOException { requestState.set(MessageState.COMPLETE); outputChannel.complete(trailers); if (!keepAlive && responseState.get() == MessageState.COMPLETE) { outputChannel.close(); } } @Override public int write(final ByteBuffer src) throws IOException { return outputChannel.write(src); } @Override public void endStream() throws IOException { endStream(null); } }; this.httpProcessor = httpProcessor; this.http1Config = http1Config; this.connectionReuseStrategy = connectionReuseStrategy; this.exchangeHandler = exchangeHandler; this.context = context; this.requestState = new AtomicReference<>(MessageState.IDLE); this.responseState = new AtomicReference<>(MessageState.HEADERS); this.requestCommitted = new AtomicBoolean(); this.done = new AtomicBoolean(); this.keepAlive = true; } boolean isRequestFinal() { return requestState.get() == MessageState.COMPLETE; } boolean isResponseFinal() { return responseState.get() == MessageState.COMPLETE; } boolean isCompleted() { return requestState.get() == MessageState.COMPLETE && responseState.get() == MessageState.COMPLETE; } String getRequestMethod() { return committedRequest != null ? committedRequest.getMethod() : null; } boolean isOutputReady() { switch (requestState.get()) { case IDLE: return true; case HEADERS: case ACK: return false; case BODY: return exchangeHandler.available() > 0; default: return false; } } private void commitRequest(final HttpRequest request, final EntityDetails entityDetails) throws IOException, HttpException { if (requestCommitted.compareAndSet(false, true)) { final ProtocolVersion transportVersion = request.getVersion(); if (transportVersion != null && !transportVersion.lessEquals(http1Config.getVersion())) { throw new UnsupportedHttpVersionException(transportVersion); } context.setProtocolVersion(transportVersion != null ? transportVersion : http1Config.getVersion()); context.setRequest(request); httpProcessor.process(request, entityDetails, context); final boolean endStream = entityDetails == null; if (endStream) { committedRequest = request; requestState.set(MessageState.COMPLETE); outputChannel.submit(request, true, FlushMode.IMMEDIATE); } else { final Header h = request.getFirstHeader(HttpHeaders.EXPECT); final boolean expectContinue = h != null && HeaderElements.CONTINUE.equalsIgnoreCase(h.getValue()); outputChannel.submit(request, false, expectContinue ? FlushMode.IMMEDIATE : FlushMode.BUFFER); committedRequest = request; if (expectContinue) { requestState.set(MessageState.ACK); timeout = outputChannel.getSocketTimeout(); final Timeout timeout = http1Config.getWaitForContinueTimeout() != null ? http1Config.getWaitForContinueTimeout() : DEFAULT_WAIT_FOR_CONTINUE; outputChannel.setSocketTimeout(timeout); } else { exchangeHandler.produce(internalDataChannel); if (requestState.compareAndSet(MessageState.HEADERS, MessageState.BODY)) { outputChannel.requestOutput(); } } } } else { throw new HttpException("Request already committed"); } } void produceOutput() throws HttpException, IOException { switch (requestState.get()) { case IDLE: requestState.set(MessageState.HEADERS); outputChannel.suspendOutput(); exchangeHandler.produceRequest((request, entityDetails, httpContext) -> commitRequest(request, entityDetails), context); break; case ACK: outputChannel.suspendOutput(); break; case BODY: exchangeHandler.produce(internalDataChannel); break; } } void consumeHeader(final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException { if (done.get() || responseState.get() != MessageState.HEADERS) { throw new ProtocolException("Unexpected message head"); } final ProtocolVersion transportVersion = response.getVersion(); if (transportVersion != null) { if (transportVersion.greaterEquals(HttpVersion.HTTP_2)) { throw new UnsupportedHttpVersionException(transportVersion); } context.setProtocolVersion(transportVersion); } final int status = response.getCode(); if (status < HttpStatus.SC_INFORMATIONAL) { throw new ProtocolException("Invalid response: " + new StatusLine(response)); } if (status > HttpStatus.SC_CONTINUE && status < HttpStatus.SC_SUCCESS) { exchangeHandler.consumeInformation(response, context); } else { if (!connectionReuseStrategy.keepAlive(committedRequest, response, context)) { keepAlive = false; } } if (requestState.get() == MessageState.ACK) { if (status == HttpStatus.SC_CONTINUE || status >= HttpStatus.SC_SUCCESS) { outputChannel.setSocketTimeout(timeout); requestState.set(MessageState.BODY); if (status < HttpStatus.SC_REDIRECTION) { exchangeHandler.produce(internalDataChannel); } } } if (status < HttpStatus.SC_SUCCESS) { return; } if (requestState.get() == MessageState.BODY) { if (status >= HttpStatus.SC_REDIRECTION) { requestState.set(MessageState.COMPLETE); if (!outputChannel.abortGracefully()) { keepAlive = false; } } } context.setProtocolVersion(transportVersion != null ? transportVersion : HttpVersion.HTTP_1_1); context.setResponse(response); httpProcessor.process(response, entityDetails, context); if (entityDetails == null && !keepAlive) { outputChannel.close(); } exchangeHandler.consumeResponse(response, entityDetails, context); responseState.set(entityDetails == null ? MessageState.COMPLETE : MessageState.BODY); } void consumeData(final ByteBuffer src) throws HttpException, IOException { if (done.get() || responseState.get() != MessageState.BODY) { throw new ProtocolException("Unexpected message data"); } exchangeHandler.consume(src); } void updateCapacity(final CapacityChannel capacityChannel) throws IOException { exchangeHandler.updateCapacity(capacityChannel); } void dataEnd(final List trailers) throws HttpException, IOException { if (done.get() || responseState.get() != MessageState.BODY) { throw new ProtocolException("Unexpected message data"); } responseState.set(MessageState.COMPLETE); if (!keepAlive && requestState.get() == MessageState.COMPLETE) { outputChannel.close(); } exchangeHandler.streamEnd(trailers); } boolean handleTimeout() { if (requestState.get() == MessageState.ACK) { requestState.set(MessageState.BODY); outputChannel.setSocketTimeout(timeout); outputChannel.requestOutput(); return true; } return false; } void failed(final Exception cause) { if (!done.get()) { exchangeHandler.failed(cause); } } @Override public void releaseResources() { if (done.compareAndSet(false, true)) { responseState.set(MessageState.COMPLETE); requestState.set(MessageState.COMPLETE); exchangeHandler.releaseResources(); } } void appendState(final StringBuilder buf) { buf.append("requestState=").append(requestState.get()) .append(", responseState=").append(responseState.get()) .append(", requestCommitted=").append(requestCommitted) .append(", keepAlive=").append(keepAlive) .append(", done=").append(done); } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append("["); appendState(buf); buf.append("]"); return buf.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestFactory.java0100644 0000000 0000000 00000004120 00000250600 030245 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.net.URI; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestFactory; import org.apache.hc.core5.http.message.BasicHttpRequest; /** * Default factory for creating {@link HttpRequest} objects. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class DefaultHttpRequestFactory implements HttpRequestFactory { public static final DefaultHttpRequestFactory INSTANCE = new DefaultHttpRequestFactory(); @Override public HttpRequest newHttpRequest(final String method, final URI uri) { return new BasicHttpRequest(method, uri); } @Override public HttpRequest newHttpRequest(final String method, final String uri) { return new BasicHttpRequest(method, uri); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestParser.java0100644 0000000 0000000 00000010265 00000250600 030101 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestFactory; import org.apache.hc.core5.http.MessageConstraintException; import org.apache.hc.core5.http.RequestHeaderFieldsTooLargeException; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.message.LineParser; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.http.nio.SessionInputBuffer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; /** * Default {@link org.apache.hc.core5.http.nio.NHttpMessageParser} implementation for {@link HttpRequest}s. * * @param The type of {@link HttpRequest}. * @since 4.1 */ public class DefaultHttpRequestParser extends AbstractMessageParser { private final HttpRequestFactory requestFactory; /** * @since 5.3 */ public DefaultHttpRequestParser( final Http1Config http1Config, final LineParser parser, final HttpRequestFactory requestFactory) { super(http1Config, parser); this.requestFactory = Args.notNull(requestFactory, "Request factory"); } /** * @since 5.3 */ public DefaultHttpRequestParser(final Http1Config http1Config, final HttpRequestFactory requestFactory) { this(http1Config, null, requestFactory); } /** * @deprecated Use {@link #DefaultHttpRequestParser(Http1Config, HttpRequestFactory)} } */ @Deprecated public DefaultHttpRequestParser(final HttpRequestFactory requestFactory, final Http1Config http1Config) { this(requestFactory, null, http1Config); } /** * @deprecated Use {@link #DefaultHttpRequestParser(Http1Config, LineParser, HttpRequestFactory)} } */ @Deprecated public DefaultHttpRequestParser( final HttpRequestFactory requestFactory, final LineParser parser, final Http1Config http1Config) { this(http1Config, parser, requestFactory); } /** * @since 4.3 */ public DefaultHttpRequestParser(final HttpRequestFactory requestFactory) { this(null, null, requestFactory); } @Override public T parse(final SessionInputBuffer sessionBuffer, final boolean endOfStream) throws IOException, HttpException { try { return super.parse(sessionBuffer, endOfStream); } catch (final MessageConstraintException ex) { throw new RequestHeaderFieldsTooLargeException(ex.getMessage(), ex); } } @Override protected T createMessage(final CharArrayBuffer buffer) throws HttpException { final RequestLine requestLine = getLineParser().parseRequestLine(buffer); final T request = this.requestFactory.newHttpRequest(requestLine.getMethod(), requestLine.getUri()); request.setVersion(requestLine.getProtocolVersion()); return request; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestParserFactory.java0100644 0000000 0000000 00000005765 00000250600 031442 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestFactory; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.message.LazyLineParser; import org.apache.hc.core5.http.message.LineParser; import org.apache.hc.core5.http.nio.NHttpMessageParser; import org.apache.hc.core5.http.nio.NHttpMessageParserFactory; /** * Default factory for request message parsers. * * @since 4.3 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class DefaultHttpRequestParserFactory implements NHttpMessageParserFactory { public static final DefaultHttpRequestParserFactory INSTANCE = new DefaultHttpRequestParserFactory(); private final Http1Config http1Config; private final LineParser lineParser; private final HttpRequestFactory requestFactory; public DefaultHttpRequestParserFactory( final Http1Config http1Config, final HttpRequestFactory requestFactory, final LineParser lineParser) { super(); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.requestFactory = requestFactory != null ? requestFactory : DefaultHttpRequestFactory.INSTANCE; this.lineParser = lineParser != null ? lineParser : LazyLineParser.INSTANCE; } public DefaultHttpRequestParserFactory(final Http1Config http1Config) { this(http1Config, null, null); } public DefaultHttpRequestParserFactory() { this(null); } @Override public NHttpMessageParser create() { return new DefaultHttpRequestParser<>(http1Config, lineParser, requestFactory); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestWriter.java0100644 0000000 0000000 00000006212 00000250600 030116 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.message.LineFormatter; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.util.CharArrayBuffer; /** * Default {@link org.apache.hc.core5.http.nio.NHttpMessageWriter} implementation for {@link HttpRequest}s. * * @param The type of {@link HttpRequest}. * @since 4.1 */ public class DefaultHttpRequestWriter extends AbstractMessageWriter { private final Http1Config http1Config; /** * @since 5.3 */ public DefaultHttpRequestWriter(final Http1Config http1Config, final LineFormatter formatter) { super(formatter); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; } /** * Creates an instance of DefaultHttpRequestWriter. * * @param formatter the line formatter If {@code null} * {@link org.apache.hc.core5.http.message.BasicLineFormatter#INSTANCE} will be used. * * @since 4.3 */ public DefaultHttpRequestWriter(final LineFormatter formatter) { this(null, formatter); } /** * @since 4.3 */ public DefaultHttpRequestWriter() { this(null, null); } /** * Determines the HTTP protocol version to be communicated to the opposite * endpoint in the message header. * * @since 5.3 */ protected HttpVersion protocolVersion(final T request) { return http1Config.getVersion(); } @Override protected void writeHeadLine(final T message, final CharArrayBuffer lineBuf) throws IOException { lineBuf.clear(); getLineFormatter().formatRequestLine(lineBuf, new RequestLine( message.getMethod(), message.getRequestUri(), protocolVersion(message))); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestWriterFactory.java0100644 0000000 0000000 00000005611 00000250600 031450 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.message.BasicLineFormatter; import org.apache.hc.core5.http.message.LineFormatter; import org.apache.hc.core5.http.nio.NHttpMessageWriter; import org.apache.hc.core5.http.nio.NHttpMessageWriterFactory; /** * Default factory for request message writers. * * @since 4.3 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class DefaultHttpRequestWriterFactory implements NHttpMessageWriterFactory { public static final DefaultHttpRequestWriterFactory INSTANCE = new DefaultHttpRequestWriterFactory(); private final Http1Config http1Config; private final LineFormatter lineFormatter; /** * @since 5.3 */ public DefaultHttpRequestWriterFactory(final Http1Config http1Config, final LineFormatter lineFormatter) { super(); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.lineFormatter = lineFormatter != null ? lineFormatter : BasicLineFormatter.INSTANCE; } /** * @since 5.3 */ public DefaultHttpRequestWriterFactory(final Http1Config http1Config) { this(http1Config, null); } public DefaultHttpRequestWriterFactory(final LineFormatter lineFormatter) { this(null, lineFormatter); } public DefaultHttpRequestWriterFactory() { this(null, null); } @Override public NHttpMessageWriter create() { return new DefaultHttpRequestWriter<>(http1Config, lineFormatter); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseFactory.java0100644 0000000 0000000 00000005531 00000250600 030422 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponseFactory; import org.apache.hc.core5.http.ReasonPhraseCatalog; import org.apache.hc.core5.http.impl.EnglishReasonPhraseCatalog; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.util.Args; /** * Default factory for creating {@link HttpResponse} objects. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class DefaultHttpResponseFactory implements HttpResponseFactory { public static final DefaultHttpResponseFactory INSTANCE = new DefaultHttpResponseFactory(); private final ReasonPhraseCatalog reasonCatalog; /** * Creates a new response factory with the given catalog. * * @param catalog the catalog of reason phrases */ public DefaultHttpResponseFactory(final ReasonPhraseCatalog catalog) { this.reasonCatalog = Args.notNull(catalog, "Reason phrase catalog"); } /** * Creates a new response factory with the default catalog. * The default catalog is {@link EnglishReasonPhraseCatalog}. */ public DefaultHttpResponseFactory() { this(EnglishReasonPhraseCatalog.INSTANCE); } @Override public HttpResponse newHttpResponse(final int status, final String reasonPhrase) { return new BasicHttpResponse(status, reasonPhrase); } @Override public HttpResponse newHttpResponse(final int status) { return new BasicHttpResponse(status, this.reasonCatalog, null); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseParser.java0100644 0000000 0000000 00000007262 00000250600 030252 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponseFactory; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.message.LineParser; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; /** * Default {@link org.apache.hc.core5.http.nio.NHttpMessageParser} implementation for {@link HttpResponse}s. * * @param The type of {@link HttpResponse}. * @since 4.1 */ public class DefaultHttpResponseParser extends AbstractMessageParser { private final HttpResponseFactory responseFactory; /** * @since 5.3 */ public DefaultHttpResponseParser( final Http1Config http1Config, final LineParser parser, final HttpResponseFactory responseFactory) { super(http1Config, parser); this.responseFactory = Args.notNull(responseFactory, "Response factory"); } /** * @since 5.3 */ public DefaultHttpResponseParser( final Http1Config http1Config, final HttpResponseFactory responseFactory) { this(http1Config, null, responseFactory); } /** * @deprecated Use {@link #DefaultHttpResponseParser(Http1Config, LineParser, HttpResponseFactory)} */ @Deprecated public DefaultHttpResponseParser( final HttpResponseFactory responseFactory, final LineParser parser, final Http1Config http1Config) { this(http1Config, parser, responseFactory); } /** * @deprecated Use {@link #DefaultHttpResponseParser(Http1Config, HttpResponseFactory)} */ @Deprecated public DefaultHttpResponseParser(final HttpResponseFactory responseFactory, final Http1Config http1Config) { this(responseFactory, null, http1Config); } /** * @since 4.3 */ public DefaultHttpResponseParser(final HttpResponseFactory responseFactory) { this(null, null, responseFactory); } @Override protected T createMessage(final CharArrayBuffer buffer) throws HttpException { final StatusLine statusLine = getLineParser().parseStatusLine(buffer); final T response = this.responseFactory.newHttpResponse(statusLine.getStatusCode(), statusLine.getReasonPhrase()); response.setVersion(statusLine.getProtocolVersion()); return response; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseParserFactory.java0100644 0000000 0000000 00000006022 00000250600 031573 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponseFactory; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.message.LazyLaxLineParser; import org.apache.hc.core5.http.message.LineParser; import org.apache.hc.core5.http.nio.NHttpMessageParser; import org.apache.hc.core5.http.nio.NHttpMessageParserFactory; /** * Default factory for response message parsers. * * @since 4.3 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class DefaultHttpResponseParserFactory implements NHttpMessageParserFactory { public static final DefaultHttpResponseParserFactory INSTANCE = new DefaultHttpResponseParserFactory(); private final Http1Config http1Config; private final HttpResponseFactory responseFactory; private final LineParser lineParser; public DefaultHttpResponseParserFactory( final Http1Config http1Config, final HttpResponseFactory responseFactory, final LineParser lineParser) { super(); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.responseFactory = responseFactory != null ? responseFactory : DefaultHttpResponseFactory.INSTANCE; this.lineParser = lineParser != null ? lineParser : LazyLaxLineParser.INSTANCE; } public DefaultHttpResponseParserFactory(final Http1Config http1Config) { this(http1Config, null, null); } public DefaultHttpResponseParserFactory() { this(null); } @Override public NHttpMessageParser create() { return new DefaultHttpResponseParser<>(http1Config, lineParser, responseFactory); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseWriter.java0100644 0000000 0000000 00000006220 00000250600 030263 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.message.LineFormatter; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.util.CharArrayBuffer; /** * Default {@link org.apache.hc.core5.http.nio.NHttpMessageWriter} implementation for {@link HttpResponse}s. * * @param The type of {@link HttpResponse}. * @since 4.1 */ public class DefaultHttpResponseWriter extends AbstractMessageWriter { private final Http1Config http1Config; /** * @since 5.3 */ public DefaultHttpResponseWriter(final LineFormatter formatter, final Http1Config http1Config) { super(formatter); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; } /** * Creates an instance of DefaultHttpResponseWriter. * * @param formatter the line formatter If {@code null} * {@link org.apache.hc.core5.http.message.BasicLineFormatter#INSTANCE} will be used. * * @since 4.3 */ public DefaultHttpResponseWriter(final LineFormatter formatter) { this(formatter, null); } /** * @since 4.3 */ public DefaultHttpResponseWriter() { this(null, null); } /** * Determines the HTTP protocol version to be communicated to the opposite * endpoint in the message header. * * @since 5.3 */ protected HttpVersion protocolVersion(final T request) { return http1Config.getVersion(); } @Override protected void writeHeadLine(final T message, final CharArrayBuffer lineBuf) throws IOException { lineBuf.clear(); getLineFormatter().formatStatusLine(lineBuf, new StatusLine( protocolVersion(message), message.getCode(), message.getReasonPhrase())); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseWriterFactory.java0100644 0000000 0000000 00000005625 00000250600 031623 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.message.BasicLineFormatter; import org.apache.hc.core5.http.message.LineFormatter; import org.apache.hc.core5.http.nio.NHttpMessageWriter; import org.apache.hc.core5.http.nio.NHttpMessageWriterFactory; /** * Default factory for response message writers. * * @since 4.3 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class DefaultHttpResponseWriterFactory implements NHttpMessageWriterFactory { public static final DefaultHttpResponseWriterFactory INSTANCE = new DefaultHttpResponseWriterFactory(); private final Http1Config http1Config; private final LineFormatter lineFormatter; /** * @since 5.3 */ public DefaultHttpResponseWriterFactory(final Http1Config http1Config, final LineFormatter lineFormatter) { super(); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.lineFormatter = lineFormatter != null ? lineFormatter : BasicLineFormatter.INSTANCE; } /** * @since 5.3 */ public DefaultHttpResponseWriterFactory(final Http1Config http1Config) { this(http1Config, null); } public DefaultHttpResponseWriterFactory(final LineFormatter lineFormatter) { this(null, lineFormatter); } public DefaultHttpResponseWriterFactory() { this(null, null); } @Override public NHttpMessageWriter create() { return new DefaultHttpResponseWriter<>(lineFormatter, http1Config); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ExpandableBuffer.java0100644 0000000 0000000 00000015775 00000250600 026337 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import org.apache.hc.core5.annotation.Internal; /** * A buffer that expand its capacity on demand. Internally, this class is backed * by an instance of {@link ByteBuffer}. *

* This class is not thread safe. *

* @since 4.0 */ @Internal public class ExpandableBuffer { /** * A buffer's mode. */ public enum Mode { /** A buffer is in the input mode. */ INPUT, /** A buffer is in the output mode. */ OUTPUT } private Mode mode; private ByteBuffer buffer; /** * Allocates buffer of the given size using the given allocator. *

* Sets the mode to {@link Mode#INPUT INPUT}. *

* * @param bufferSize the buffer size. */ protected ExpandableBuffer(final int bufferSize) { super(); this.buffer = ByteBuffer.allocate(bufferSize); this.mode = Mode.INPUT; } /** * Returns the current mode: *
    *
  • {@link Mode#INPUT INPUT}: the buffer is in the input mode.
  • *
  • {@link Mode#OUTPUT OUTPUT}: the buffer is in the output mode.
  • *
* * @return current input/output mode, never null. */ protected Mode mode() { return this.mode; } protected ByteBuffer buffer() { return this.buffer; } /** * Sets the mode to {@link Mode#OUTPUT OUTPUT}. The buffer can now be read from. */ protected void setOutputMode() { if (this.mode != Mode.OUTPUT) { this.buffer.flip(); this.mode = Mode.OUTPUT; } } /** * Sets the mode to {@link Mode#INPUT INPUT}. The buffer can now be written into. */ protected void setInputMode() { if (this.mode != Mode.INPUT) { if (this.buffer.hasRemaining()) { this.buffer.compact(); } else { this.buffer.clear(); } this.mode = Mode.INPUT; } } private void expandCapacity(final int capacity) { final ByteBuffer oldBuffer = this.buffer; this.buffer = ByteBuffer.allocate(capacity); oldBuffer.flip(); this.buffer.put(oldBuffer); } /** * Expands buffer's capacity. * * @throws BufferOverflowException in case we get over the maximum allowed value */ protected void expand() throws BufferOverflowException { int newcapacity = (this.buffer.capacity() + 1) << 1; if (newcapacity < 0) { final int vmBytes = Long.SIZE >> 3; final int javaBytes = 8; // this is to be checked when the JVM version changes @SuppressWarnings("unused") // we really need the 8 if we're going to make this foolproof final int headRoom = Math.max(vmBytes, javaBytes); // Reason: In GC the size of objects is passed as int (2 bytes). // Then, the header size of the objects is added to the size. // Long has the longest header available. Object header seems to be linked to it. // Details: I added a minimum of 8 just to be safe and because 8 is used in // java.lang.Object.ArrayList: private static final int MAX_ARRAY_SIZE = 2147483639. // // WARNING: This code assumes you are providing enough heap room with -Xmx. // source of inspiration: https://bugs.openjdk.java.net/browse/JDK-8059914 newcapacity = Integer.MAX_VALUE - headRoom; if (newcapacity <= this.buffer.capacity()) { throw new BufferOverflowException(); } } expandCapacity(newcapacity); } /** * Ensures the buffer can accommodate the exact required capacity. * * @param requiredCapacity the required capacity. */ protected void ensureCapacity(final int requiredCapacity) { if (requiredCapacity > this.buffer.capacity()) { expandCapacity(requiredCapacity); } } /** * Ensures the buffer can accommodate at least the required capacity adjusted to multiples of 1024. * * @param requiredCapacity the required capacity. */ protected void ensureAdjustedCapacity(final int requiredCapacity) { if (requiredCapacity > this.buffer.capacity()) { final int adjustedCapacity = ((requiredCapacity >> 10) + 1) << 10; expandCapacity(adjustedCapacity); } } /** * Determines if the buffer contains data. *

* Sets the mode to output. *

* * @return {@code true} if there is data in the buffer, * {@code false} otherwise. */ protected boolean hasData() { setOutputMode(); return this.buffer.hasRemaining(); } /** * Returns the length of this buffer. *

* Sets the mode to output. *

* * @return buffer length. */ protected int length() { setOutputMode(); return this.buffer.remaining(); } /** * Returns available capacity of this buffer. * * @return buffer length. */ protected int capacity() { setInputMode(); return this.buffer.remaining(); } /** * Clears buffer. *

* Sets the mode to {@link Mode#INPUT INPUT}. *

*/ protected void clear() { this.buffer.clear(); this.mode = Mode.INPUT; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("[mode="); sb.append(this.mode); sb.append(" pos="); sb.append(this.buffer.position()); sb.append(" lim="); sb.append(this.buffer.limit()); sb.append(" cap="); sb.append(this.buffer.capacity()); sb.append("]"); return sb.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/FlushMode.java0100644 0000000 0000000 00000002367 00000250600 025021 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; enum FlushMode { IMMEDIATE, BUFFER } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/Http1StreamChannel.java0100644 0000000 0000000 00000003744 00000250600 026600 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpMessage; import org.apache.hc.core5.http.nio.ContentEncoder; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.util.Timeout; interface Http1StreamChannel extends ContentEncoder { void close(); void close(CloseMode closeMode); void activate() throws HttpException, IOException; void submit(OutgoingMessage messageHead, boolean endStream, final FlushMode flushMode) throws HttpException, IOException; void requestOutput(); void suspendOutput() throws IOException; boolean abortGracefully() throws IOException; Timeout getSocketTimeout(); void setSocketTimeout(Timeout timeout); } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/HttpConnectionEventHandler.java0100644 0000000 0000000 00000003051 00000250600 030361 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.reactor.IOEventHandler; /** * {@link IOEventHandler} that also exposes {@link HttpConnection} properties. * * @since 5.0 */ @Internal public interface HttpConnectionEventHandler extends IOEventHandler, HttpConnection { } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/IdentityDecoder.java0100644 0000000 0000000 00000010422 00000250600 026201 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.nio.FileContentDecoder; import org.apache.hc.core5.http.nio.SessionInputBuffer; import org.apache.hc.core5.util.Args; /** * Content decoder that reads data without any transformation. The end of the * content entity is delineated by closing the underlying connection * (EOF condition). Entities transferred using this input stream can be of * unlimited length. *

* This decoder is optimized to transfer data directly from the underlying * I/O session's channel to a {@link FileChannel}, whenever * possible avoiding intermediate buffering in the session buffer. * * @since 4.0 */ public class IdentityDecoder extends AbstractContentDecoder implements FileContentDecoder { public IdentityDecoder( final ReadableByteChannel channel, final SessionInputBuffer buffer, final BasicHttpTransportMetrics metrics) { super(channel, buffer, metrics); } @Override public int read(final ByteBuffer dst) throws IOException { Args.notNull(dst, "Byte buffer"); if (isCompleted()) { return -1; } final int bytesRead; if (this.buffer.hasData()) { bytesRead = this.buffer.read(dst); } else { bytesRead = readFromChannel(dst); } if (bytesRead == -1) { setCompleted(); } return bytesRead; } @Override public long transfer( final FileChannel dst, final long position, final long count) throws IOException { if (dst == null) { return 0; } if (isCompleted()) { return 0; } long bytesRead; if (this.buffer.hasData()) { final int maxLen = this.buffer.length(); dst.position(position); bytesRead = this.buffer.read(dst, count < maxLen ? (int)count : maxLen); } else { if (this.channel.isOpen()) { if (position > dst.size()) { throw new IOException("Position past end of file [" + position + " > " + dst.size() + "]"); } bytesRead = dst.transferFrom(this.channel, position, count); if (count > 0 && bytesRead == 0) { bytesRead = this.buffer.fill(this.channel); } } else { bytesRead = -1; } if (bytesRead > 0) { this.metrics.incrementBytesTransferred(bytesRead); } } if (bytesRead == -1) { setCompleted(); } return bytesRead; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("[identity; completed: "); sb.append(this.completed); sb.append("]"); return sb.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/IdentityEncoder.java0100644 0000000 0000000 00000012201 00000250600 026210 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.WritableByteChannel; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.nio.FileContentEncoder; import org.apache.hc.core5.http.nio.SessionOutputBuffer; /** * Content encoder that writes data without any transformation. The end of * the content entity is demarcated by closing the underlying connection * (EOF condition). Entities transferred using this input stream can be of * unlimited length. *

* This decoder is optimized to transfer data directly from * a {@link FileChannel} to the underlying I/O session's channel whenever * possible avoiding intermediate buffering in the session buffer. * * @since 4.0 */ public class IdentityEncoder extends AbstractContentEncoder implements FileContentEncoder { private final int fragHint; /** * @since 4.3 * * @param channel underlying channel. * @param buffer session buffer. * @param metrics transport metrics. * @param chunkSizeHint fragment size hint defining an minimal size of a fragment * that should be written out directly to the channel bypassing the session buffer. * Value {@code 0} disables fragment buffering. */ public IdentityEncoder( final WritableByteChannel channel, final SessionOutputBuffer buffer, final BasicHttpTransportMetrics metrics, final int chunkSizeHint) { super(channel, buffer, metrics); this.fragHint = Math.max(chunkSizeHint, 0); } public IdentityEncoder( final WritableByteChannel channel, final SessionOutputBuffer buffer, final BasicHttpTransportMetrics metrics) { this(channel, buffer, metrics, 0); } @Override public int write(final ByteBuffer src) throws IOException { if (src == null) { return 0; } assertNotCompleted(); int total = 0; while (src.hasRemaining()) { if (this.buffer.hasData() || this.fragHint > 0) { if (src.remaining() <= this.fragHint) { final int capacity = this.fragHint - this.buffer.length(); if (capacity > 0) { final int limit = Math.min(capacity, src.remaining()); final int bytesWritten = writeToBuffer(src, limit); total += bytesWritten; } } } if (this.buffer.hasData()) { if (this.buffer.length() >= this.fragHint || src.hasRemaining()) { final int bytesWritten = flushToChannel(); if (bytesWritten == 0) { break; } } } if (!this.buffer.hasData() && src.remaining() > this.fragHint) { final int bytesWritten = writeToChannel(src); total += bytesWritten; if (bytesWritten == 0) { break; } } } return total; } @Override public long transfer( final FileChannel src, final long position, final long count) throws IOException { if (src == null) { return 0; } assertNotCompleted(); flushToChannel(); if (this.buffer.hasData()) { return 0; } final long bytesWritten = src.transferTo(position, count, this.channel); if (bytesWritten > 0) { this.metrics.incrementBytesTransferred(bytesWritten); } return bytesWritten; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("[identity; completed: "); sb.append(isCompleted()); sb.append("]"); return sb.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/LengthDelimitedDecoder.java0100644 0000000 0000000 00000013521 00000250600 027455 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.nio.FileContentDecoder; import org.apache.hc.core5.http.nio.SessionInputBuffer; import org.apache.hc.core5.util.Args; /** * Content decoder that cuts off after a defined number of bytes. This class * is used to receive content of HTTP messages where the end of the content * entity is determined by the value of the {@code Content-Length header}. * Entities transferred using this stream can be maximum {@link Long#MAX_VALUE} * long. *

* This decoder is optimized to transfer data directly from the underlying * I/O session's channel to a {@link FileChannel}, whenever * possible avoiding intermediate buffering in the session buffer. * * @since 4.0 */ public class LengthDelimitedDecoder extends AbstractContentDecoder implements FileContentDecoder { private final long contentLength; private long len; public LengthDelimitedDecoder( final ReadableByteChannel channel, final SessionInputBuffer buffer, final BasicHttpTransportMetrics metrics, final long contentLength) { super(channel, buffer, metrics); Args.notNegative(contentLength, "Content length"); this.contentLength = contentLength; } @Override public int read(final ByteBuffer dst) throws IOException { Args.notNull(dst, "Byte buffer"); if (isCompleted()) { return -1; } final int chunk = (int) Math.min(this.contentLength - this.len, Integer.MAX_VALUE); final int bytesRead; if (this.buffer.hasData()) { final int maxLen = Math.min(chunk, this.buffer.length()); bytesRead = this.buffer.read(dst, maxLen); } else { bytesRead = readFromChannel(dst, chunk); } if (bytesRead == -1) { setCompleted(); if (this.len < this.contentLength) { throw new ConnectionClosedException( "Premature end of Content-Length delimited message body (expected: %d; received: %d)", this.contentLength, this.len); } } this.len += bytesRead; if (this.len >= this.contentLength) { this.completed = true; } if (this.completed && bytesRead == 0) { return -1; } return bytesRead; } @Override public long transfer( final FileChannel dst, final long position, final long count) throws IOException { if (dst == null) { return 0; } if (isCompleted()) { return -1; } final int chunk = (int) Math.min(this.contentLength - this.len, Integer.MAX_VALUE); final long bytesRead; if (this.buffer.hasData()) { final int maxLen = Math.min(chunk, this.buffer.length()); dst.position(position); bytesRead = this.buffer.read(dst, count < maxLen ? (int)count : maxLen); } else { if (this.channel.isOpen()) { if (position > dst.size()) { throw new IOException(String.format("Position past end of file [%d > %d]", position, dst.size())); } bytesRead = dst.transferFrom(this.channel, position, count < chunk ? count : chunk); } else { bytesRead = -1; } if (bytesRead > 0) { this.metrics.incrementBytesTransferred(bytesRead); } } if (bytesRead == -1) { setCompleted(); if (this.len < this.contentLength) { throw new ConnectionClosedException( "Premature end of Content-Length delimited message body (expected: %d; received: %d)", this.contentLength, this.len); } } this.len += bytesRead; if (this.len >= this.contentLength) { this.completed = true; } return bytesRead; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("[content length: "); sb.append(this.contentLength); sb.append("; pos: "); sb.append(this.len); sb.append("; completed: "); sb.append(this.completed); sb.append("]"); return sb.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/LengthDelimitedEncoder.java0100644 0000000 0000000 00000014701 00000250600 027470 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.WritableByteChannel; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.nio.FileContentEncoder; import org.apache.hc.core5.http.nio.SessionOutputBuffer; import org.apache.hc.core5.util.Args; /** * Content encoder that cuts off after a defined number of bytes. This class * is used to send content of HTTP messages where the end of the content entity * is determined by the value of the {@code Content-Length header}. * Entities transferred using this stream can be maximum {@link Long#MAX_VALUE} * long. *

* This decoder is optimized to transfer data directly from * a {@link FileChannel} to the underlying I/O session's channel whenever * possible avoiding intermediate buffering in the session buffer. * * @since 4.0 */ public class LengthDelimitedEncoder extends AbstractContentEncoder implements FileContentEncoder { private final long contentLength; private final int fragHint; private long remaining; /** * @since 4.3 * * @param channel underlying channel. * @param buffer session buffer. * @param metrics transport metrics. * @param contentLength content length. * @param chunkSizeHint fragment size hint defining an minimal size of a fragment * that should be written out directly to the channel bypassing the session buffer. * Value {@code 0} disables fragment buffering. */ public LengthDelimitedEncoder( final WritableByteChannel channel, final SessionOutputBuffer buffer, final BasicHttpTransportMetrics metrics, final long contentLength, final int chunkSizeHint) { super(channel, buffer, metrics); Args.notNegative(contentLength, "Content length"); this.contentLength = contentLength; this.fragHint = Math.max(chunkSizeHint, 0); this.remaining = contentLength; } public LengthDelimitedEncoder( final WritableByteChannel channel, final SessionOutputBuffer buffer, final BasicHttpTransportMetrics metrics, final long contentLength) { this(channel, buffer, metrics, contentLength, 0); } private int nextChunk(final ByteBuffer src) { return (int) Math.min(Math.min(this.remaining, Integer.MAX_VALUE), src.remaining()); } @Override public int write(final ByteBuffer src) throws IOException { if (src == null) { return 0; } assertNotCompleted(); int total = 0; while (src.hasRemaining() && this.remaining > 0) { if (this.buffer.hasData() || this.fragHint > 0) { final int chunk = nextChunk(src); if (chunk <= this.fragHint) { final int capacity = this.fragHint - this.buffer.length(); if (capacity > 0) { final int limit = Math.min(capacity, chunk); final int bytesWritten = writeToBuffer(src, limit); this.remaining -= bytesWritten; total += bytesWritten; } } } if (this.buffer.hasData()) { final int chunk = nextChunk(src); if (this.buffer.length() >= this.fragHint || chunk > 0) { final int bytesWritten = flushToChannel(); if (bytesWritten == 0) { break; } } } if (!this.buffer.hasData()) { final int chunk = nextChunk(src); if (chunk > this.fragHint) { final int bytesWritten = writeToChannel(src, chunk); this.remaining -= bytesWritten; total += bytesWritten; if (bytesWritten == 0) { break; } } } } if (this.remaining <= 0) { super.complete(null); } return total; } @Override public long transfer( final FileChannel src, final long position, final long count) throws IOException { if (src == null) { return 0; } assertNotCompleted(); flushToChannel(); if (this.buffer.hasData()) { return 0; } final long chunk = Math.min(this.remaining, count); final long bytesWritten = src.transferTo(position, chunk, this.channel); if (bytesWritten > 0) { this.metrics.incrementBytesTransferred(bytesWritten); } this.remaining -= bytesWritten; if (this.remaining <= 0) { super.complete(null); } return bytesWritten; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("[content length: "); sb.append(this.contentLength); sb.append("; pos: "); sb.append(this.contentLength - this.remaining); sb.append("; completed: "); sb.append(isCompleted()); sb.append("]"); return sb.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/MessageState.java0100644 0000000 0000000 00000002414 00000250600 025511 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; public enum MessageState { IDLE, HEADERS, ACK, BODY, COMPLETE } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1IOEventHandler.java0100644 0000000 0000000 00000004550 00000250600 030046 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.net.InetAddressUtils; /** * {@link org.apache.hc.core5.reactor.IOEventHandler} that implements * server side HTTP/1.1 messaging protocol with full support for * duplexed message transmission and message pipelining. * * @since 5.0 */ public class ServerHttp1IOEventHandler extends AbstractHttp1IOEventHandler { public ServerHttp1IOEventHandler(final ServerHttp1StreamDuplexer streamDuplexer) { super(streamDuplexer); } @Override public String toString() { final StringBuilder buf = new StringBuilder(); InetAddressUtils.formatAddress(buf, getRemoteAddress()); buf.append("->"); InetAddressUtils.formatAddress(buf, getLocalAddress()); buf.append(" ["); streamDuplexer.appendState(buf); buf.append("]"); return buf.toString(); } @Override public ProtocolVersion getProtocolVersion() { final ProtocolVersion protocolVersion = super.getProtocolVersion(); return protocolVersion != null ? protocolVersion : HttpVersion.HTTP_1_1; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1IOEventHandlerFactory.java0100644 0000000 0000000 00000007112 00000250600 031373 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.reactor.EndpointParameters; import org.apache.hc.core5.reactor.IOEventHandler; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; /** * {@link ServerHttp1IOEventHandler} factory. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class ServerHttp1IOEventHandlerFactory implements IOEventHandlerFactory { private final ServerHttp1StreamDuplexerFactory streamDuplexerFactory; private final TlsStrategy tlsStrategy; private final Timeout handshakeTimeout; public ServerHttp1IOEventHandlerFactory( final ServerHttp1StreamDuplexerFactory streamDuplexerFactory, final TlsStrategy tlsStrategy, final Timeout handshakeTimeout) { this.streamDuplexerFactory = Args.notNull(streamDuplexerFactory, "Stream duplexer factory"); this.tlsStrategy = tlsStrategy; this.handshakeTimeout = handshakeTimeout; } @Override public IOEventHandler createHandler(final ProtocolIOSession ioSession, final Object attachment) { String endpointScheme = URIScheme.HTTP.id; if (tlsStrategy != null) { if (attachment instanceof EndpointParameters) { final EndpointParameters params = (EndpointParameters) attachment; endpointScheme = params.getScheme(); if (URIScheme.HTTPS.same(endpointScheme)) { tlsStrategy.upgrade( ioSession, params, params.getAttachment(), handshakeTimeout, null); } } else { endpointScheme = URIScheme.HTTPS.id; tlsStrategy.upgrade( ioSession, null, attachment, handshakeTimeout, null); } } return new ServerHttp1IOEventHandler(streamDuplexerFactory.create(endpointScheme, ioSession)); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamDuplexer.java0100644 0000000 0000000 00000052061 00000250600 030203 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.BasicHttpConnectionMetrics; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.ContentDecoder; import org.apache.hc.core5.http.nio.ContentEncoder; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.nio.NHttpMessageParser; import org.apache.hc.core5.http.nio.NHttpMessageWriter; import org.apache.hc.core5.http.nio.SessionInputBuffer; import org.apache.hc.core5.http.nio.SessionOutputBuffer; import org.apache.hc.core5.http.nio.command.RequestExecutionCommand; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; import org.apache.hc.core5.util.Timeout; /** * I/O event handler for events fired by {@link ProtocolIOSession} that implements * server side HTTP/1.1 messaging protocol with full support for * duplexed message transmission and message pipelining. * * @since 5.0 */ @Internal public class ServerHttp1StreamDuplexer extends AbstractHttp1StreamDuplexer { private final String scheme; private final HttpProcessor httpProcessor; private final HandlerFactory exchangeHandlerFactory; private final Http1Config http1Config; private final ConnectionReuseStrategy connectionReuseStrategy; private final Http1StreamListener streamListener; private final Callback exceptionCallback; private final Queue pipeline; private final Http1StreamChannel outputChannel; private volatile ServerHttp1StreamHandler outgoing; private volatile ServerHttp1StreamHandler incoming; public ServerHttp1StreamDuplexer( final ProtocolIOSession ioSession, final HttpProcessor httpProcessor, final HandlerFactory exchangeHandlerFactory, final String scheme, final Http1Config http1Config, final CharCodingConfig charCodingConfig, final ConnectionReuseStrategy connectionReuseStrategy, final NHttpMessageParser incomingMessageParser, final NHttpMessageWriter outgoingMessageWriter, final ContentLengthStrategy incomingContentStrategy, final ContentLengthStrategy outgoingContentStrategy, final Http1StreamListener streamListener, final Callback exceptionCallback) { super(ioSession, http1Config, charCodingConfig, incomingMessageParser, outgoingMessageWriter, incomingContentStrategy, outgoingContentStrategy); this.httpProcessor = Args.notNull(httpProcessor, "HTTP processor"); this.exchangeHandlerFactory = Args.notNull(exchangeHandlerFactory, "Exchange handler factory"); this.scheme = scheme != null ? scheme : URIScheme.HTTP.getId(); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.connectionReuseStrategy = connectionReuseStrategy != null ? connectionReuseStrategy : DefaultConnectionReuseStrategy.INSTANCE; this.streamListener = streamListener; this.exceptionCallback = exceptionCallback; this.pipeline = new ConcurrentLinkedQueue<>(); this.outputChannel = new Http1StreamChannel() { @Override public void close() { ServerHttp1StreamDuplexer.this.close(CloseMode.GRACEFUL); } @Override public void close(final CloseMode closeMode) { ServerHttp1StreamDuplexer.this.close(closeMode); } @Override public void submit( final HttpResponse response, final boolean endStream, final FlushMode flushMode) throws HttpException, IOException { if (streamListener != null) { streamListener.onResponseHead(ServerHttp1StreamDuplexer.this, response); } commitMessageHead(response, endStream, flushMode); } @Override public void requestOutput() { requestSessionOutput(); } @Override public void suspendOutput() throws IOException { suspendSessionOutput(); } @Override public Timeout getSocketTimeout() { return getSessionTimeout(); } @Override public void setSocketTimeout(final Timeout timeout) { setSessionTimeout(timeout); } @Override public int write(final ByteBuffer src) throws IOException { return streamOutput(src); } @Override public void complete(final List trailers) throws IOException { endOutputStream(trailers); } @Override public boolean isCompleted() { return isOutputCompleted(); } @Override public boolean abortGracefully() throws IOException { final MessageDelineation messageDelineation = endOutputStream(null); return messageDelineation != MessageDelineation.MESSAGE_HEAD; } @Override public void activate() throws HttpException, IOException { // empty } @Override public String toString() { return "Http1StreamChannel[" + ServerHttp1StreamDuplexer.this + "]"; } }; } @Override void terminate(final Exception exception) { if (incoming != null) { incoming.failed(exception); incoming.releaseResources(); incoming = null; } if (outgoing != null) { outgoing.failed(exception); outgoing.releaseResources(); outgoing = null; } for (;;) { final ServerHttp1StreamHandler handler = pipeline.poll(); if (handler != null) { handler.failed(exception); handler.releaseResources(); } else { break; } } } @Override void disconnected() { if (incoming != null) { if (!incoming.isCompleted()) { incoming.failed(new ConnectionClosedException()); } incoming.releaseResources(); incoming = null; } if (outgoing != null) { if (!outgoing.isCompleted()) { outgoing.failed(new ConnectionClosedException()); } outgoing.releaseResources(); outgoing = null; } for (;;) { final ServerHttp1StreamHandler handler = pipeline.poll(); if (handler != null) { handler.failed(new ConnectionClosedException()); handler.releaseResources(); } else { break; } } } @Override void updateInputMetrics(final HttpRequest request, final BasicHttpConnectionMetrics connMetrics) { connMetrics.incrementRequestCount(); } @Override void updateOutputMetrics(final HttpResponse response, final BasicHttpConnectionMetrics connMetrics) { if (response.getCode() >= HttpStatus.SC_OK) { connMetrics.incrementResponseCount(); } } @Override protected boolean handleIncomingMessage(final HttpRequest request) throws HttpException { return true; } @Override protected ContentDecoder createContentDecoder( final long len, final ReadableByteChannel channel, final SessionInputBuffer buffer, final BasicHttpTransportMetrics metrics) throws HttpException { if (len >= 0) { return new LengthDelimitedDecoder(channel, buffer, metrics, len); } else if (len == ContentLengthStrategy.CHUNKED) { return new ChunkDecoder(channel, buffer, http1Config, metrics); } else { return null; } } @Override protected boolean handleOutgoingMessage(final HttpResponse response) throws HttpException { return true; } @Override protected ContentEncoder createContentEncoder( final long len, final WritableByteChannel channel, final SessionOutputBuffer buffer, final BasicHttpTransportMetrics metrics) throws HttpException { final int chunkSizeHint = http1Config.getChunkSizeHint() >= 0 ? http1Config.getChunkSizeHint() : 2048; if (len >= 0) { return new LengthDelimitedEncoder(channel, buffer, metrics, len, chunkSizeHint); } else if (len == ContentLengthStrategy.CHUNKED) { return new ChunkEncoder(channel, buffer, metrics, chunkSizeHint); } else { return new IdentityEncoder(channel, buffer, metrics, chunkSizeHint); } } @Override boolean inputIdle() { return incoming == null; } @Override boolean outputIdle() { return outgoing == null && pipeline.isEmpty(); } @Override HttpRequest parseMessageHead(final boolean endOfStream) throws IOException, HttpException { try { return super.parseMessageHead(endOfStream); } catch (final HttpException ex) { terminateExchange(ex); return null; } } void terminateExchange(final HttpException ex) throws HttpException, IOException { suspendSessionInput(); final ServerHttp1StreamHandler streamHandler; final HttpCoreContext context = HttpCoreContext.create(); context.setSSLSession(getSSLSession()); context.setEndpointDetails(getEndpointDetails()); if (outgoing == null) { streamHandler = new ServerHttp1StreamHandler( outputChannel, httpProcessor, http1Config, connectionReuseStrategy, exchangeHandlerFactory, exceptionCallback, context); outgoing = streamHandler; } else { streamHandler = new ServerHttp1StreamHandler( new DelayedOutputChannel(outputChannel), httpProcessor, http1Config, connectionReuseStrategy, exchangeHandlerFactory, exceptionCallback, context); pipeline.add(streamHandler); } streamHandler.terminateExchange(ex); incoming = null; } @Override void consumeHeader(final HttpRequest request, final EntityDetails entityDetails) throws HttpException, IOException { if (streamListener != null) { streamListener.onRequestHead(this, request); } final ServerHttp1StreamHandler streamHandler; final HttpCoreContext context = HttpCoreContext.create(); context.setSSLSession(getSSLSession()); context.setEndpointDetails(getEndpointDetails()); if (outgoing == null) { streamHandler = new ServerHttp1StreamHandler( outputChannel, httpProcessor, http1Config, connectionReuseStrategy, exchangeHandlerFactory, exceptionCallback, context); outgoing = streamHandler; } else { streamHandler = new ServerHttp1StreamHandler( new DelayedOutputChannel(outputChannel), httpProcessor, http1Config, connectionReuseStrategy, exchangeHandlerFactory, exceptionCallback, context); pipeline.add(streamHandler); } request.setScheme(scheme); streamHandler.consumeHeader(request, entityDetails); incoming = streamHandler; } @Override void consumeData(final ByteBuffer src) throws HttpException, IOException { Asserts.notNull(incoming, "Request stream handler"); incoming.consumeData(src); } @Override void updateCapacity(final CapacityChannel capacityChannel) throws HttpException, IOException { Asserts.notNull(incoming, "Request stream handler"); incoming.updateCapacity(capacityChannel); } @Override void dataEnd(final List trailers) throws HttpException, IOException { Asserts.notNull(incoming, "Request stream handler"); incoming.dataEnd(trailers); } @Override void inputEnd() throws HttpException, IOException { if (incoming != null) { if (incoming.isCompleted()) { incoming.releaseResources(); } incoming = null; } if (isShuttingDown() && outputIdle() && inputIdle()) { shutdownSession(CloseMode.IMMEDIATE); } } @Override void execute(final RequestExecutionCommand executionCommand) throws HttpException { throw new HttpException("Illegal command: " + executionCommand.getClass()); } @Override boolean isRequestInitiated() { return false; } @Override boolean isOutputReady() { return outgoing != null && outgoing.isOutputReady(); } @Override void produceOutput() throws HttpException, IOException { if (outgoing != null) { outgoing.produceOutput(); } } @Override void outputEnd() throws HttpException, IOException { if (outgoing != null && outgoing.isResponseFinal()) { if (streamListener != null) { streamListener.onExchangeComplete(this, outgoing.keepAlive()); } if (outgoing.isCompleted()) { outgoing.releaseResources(); } outgoing = null; } if (outgoing == null && isActive()) { final ServerHttp1StreamHandler handler = pipeline.poll(); if (handler != null) { outgoing = handler; handler.activateChannel(); if (handler.isOutputReady()) { handler.produceOutput(); } } } if (isShuttingDown() && outputIdle() && inputIdle()) { shutdownSession(CloseMode.IMMEDIATE); } } @Override boolean handleTimeout() { return false; } @Override void appendState(final StringBuilder buf) { super.appendState(buf); buf.append(", incoming=["); if (incoming != null) { incoming.appendState(buf); } buf.append("], outgoing=["); if (outgoing != null) { outgoing.appendState(buf); } buf.append("], pipeline="); buf.append(pipeline.size()); } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append("["); appendState(buf); buf.append("]"); return buf.toString(); } private static class DelayedOutputChannel implements Http1StreamChannel { private final Http1StreamChannel channel; private volatile boolean direct; private volatile HttpResponse delayedResponse; private volatile boolean completed; private final ReentrantLock lock = new ReentrantLock(); private DelayedOutputChannel(final Http1StreamChannel channel) { this.channel = channel; } @Override public void close() { channel.close(); } public void close(final CloseMode closeMode) { channel.close(closeMode); } @Override public void submit( final HttpResponse response, final boolean endStream, final FlushMode flushMode) throws HttpException, IOException { lock.lock(); try { if (direct) { channel.submit(response, endStream, flushMode); } else { delayedResponse = response; completed = endStream; } } finally { lock.unlock(); } } @Override public void suspendOutput() throws IOException { channel.suspendOutput(); } @Override public void requestOutput() { channel.requestOutput(); } @Override public Timeout getSocketTimeout() { return channel.getSocketTimeout(); } @Override public void setSocketTimeout(final Timeout timeout) { channel.setSocketTimeout(timeout); } @Override public int write(final ByteBuffer src) throws IOException { lock.lock(); try { return direct ? channel.write(src) : 0; } finally { lock.unlock(); } } @Override public void complete(final List trailers) throws IOException { lock.lock(); try { if (direct) { channel.complete(trailers); } else { completed = true; } } finally { lock.unlock(); } } @Override public boolean abortGracefully() throws IOException { lock.lock(); try { if (direct) { return channel.abortGracefully(); } completed = true; return true; } finally { lock.unlock(); } } @Override public boolean isCompleted() { lock.lock(); try { return direct ? channel.isCompleted() : completed; } finally { lock.unlock(); } } @Override public void activate() throws IOException, HttpException { lock.lock(); try { direct = true; if (delayedResponse != null) { channel.submit(delayedResponse, completed, completed ? FlushMode.IMMEDIATE : FlushMode.BUFFER); delayedResponse = null; } } finally { lock.unlock(); } } } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamDuplexerFactory.java0100644 0000000 0000000 00000016045 00000250600 031535 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.nio.NHttpMessageParserFactory; import org.apache.hc.core5.http.nio.NHttpMessageWriterFactory; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.util.Args; /** * {@link ServerHttp1StreamDuplexer} factory. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) @Internal public final class ServerHttp1StreamDuplexerFactory { private final HttpProcessor httpProcessor; private final HandlerFactory exchangeHandlerFactory; private final ConnectionReuseStrategy connectionReuseStrategy; private final Http1Config http1Config; private final CharCodingConfig charCodingConfig; private final NHttpMessageParserFactory requestParserFactory; private final NHttpMessageWriterFactory responseWriterFactory; private final ContentLengthStrategy incomingContentStrategy; private final ContentLengthStrategy outgoingContentStrategy; private final Http1StreamListener streamListener; private final Callback exceptionCallback; public ServerHttp1StreamDuplexerFactory( final HttpProcessor httpProcessor, final HandlerFactory exchangeHandlerFactory, final Http1Config http1Config, final CharCodingConfig charCodingConfig, final ConnectionReuseStrategy connectionReuseStrategy, final NHttpMessageParserFactory requestParserFactory, final NHttpMessageWriterFactory responseWriterFactory, final ContentLengthStrategy incomingContentStrategy, final ContentLengthStrategy outgoingContentStrategy, final Http1StreamListener streamListener, final Callback exceptionCallback) { this.httpProcessor = Args.notNull(httpProcessor, "HTTP processor"); this.exchangeHandlerFactory = Args.notNull(exchangeHandlerFactory, "Exchange handler factory"); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.charCodingConfig = charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT; this.connectionReuseStrategy = connectionReuseStrategy != null ? connectionReuseStrategy : DefaultConnectionReuseStrategy.INSTANCE; this.requestParserFactory = requestParserFactory != null ? requestParserFactory : new DefaultHttpRequestParserFactory(this.http1Config); this.responseWriterFactory = responseWriterFactory != null ? responseWriterFactory : new DefaultHttpResponseWriterFactory(this.http1Config); this.incomingContentStrategy = incomingContentStrategy != null ? incomingContentStrategy : DefaultContentLengthStrategy.INSTANCE; this.outgoingContentStrategy = outgoingContentStrategy != null ? outgoingContentStrategy : DefaultContentLengthStrategy.INSTANCE; this.streamListener = streamListener; this.exceptionCallback = exceptionCallback; } public ServerHttp1StreamDuplexerFactory( final HttpProcessor httpProcessor, final HandlerFactory exchangeHandlerFactory, final Http1Config http1Config, final CharCodingConfig charCodingConfig, final ConnectionReuseStrategy connectionReuseStrategy, final NHttpMessageParserFactory requestParserFactory, final NHttpMessageWriterFactory responseWriterFactory, final Http1StreamListener streamListener, final Callback exceptionCallback) { this(httpProcessor, exchangeHandlerFactory, http1Config, charCodingConfig, connectionReuseStrategy, requestParserFactory, responseWriterFactory, null, null, streamListener, exceptionCallback); } public ServerHttp1StreamDuplexerFactory( final HttpProcessor httpProcessor, final HandlerFactory exchangeHandlerFactory, final Http1Config http1Config, final CharCodingConfig charCodingConfig, final Http1StreamListener streamListener, final Callback exceptionCallback) { this(httpProcessor, exchangeHandlerFactory, http1Config, charCodingConfig, null, null ,null, streamListener, exceptionCallback); } public ServerHttp1StreamDuplexer create(final String scheme, final ProtocolIOSession ioSession) { return new ServerHttp1StreamDuplexer(ioSession, httpProcessor, exchangeHandlerFactory, scheme, http1Config, charCodingConfig, connectionReuseStrategy, requestParserFactory.create(), responseWriterFactory.create(), incomingContentStrategy, outgoingContentStrategy, streamListener, exceptionCallback); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamHandler.java0100644 0000000 0000000 00000037147 00000250600 030000 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.MisdirectedRequestException; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.UnsupportedHttpVersionException; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.ServerSupport; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.nio.AsyncPushProducer; import org.apache.hc.core5.http.nio.AsyncResponseProducer; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.nio.ResourceHolder; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.nio.support.BasicResponseProducer; import org.apache.hc.core5.http.nio.support.ImmediateResponseExchangeHandler; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.io.CloseMode; class ServerHttp1StreamHandler implements ResourceHolder { private final Http1StreamChannel outputChannel; private final DataStreamChannel internalDataChannel; private final ResponseChannel responseChannel; private final HttpProcessor httpProcessor; private final Http1Config http1Config; private final ConnectionReuseStrategy connectionReuseStrategy; private final HandlerFactory exchangeHandlerFactory; private final Callback exceptionCallback; private final HttpCoreContext context; private final AtomicReference requestState; private final AtomicReference responseState; private final AtomicBoolean responseCommitted; private final AtomicBoolean done; private volatile boolean keepAlive; private volatile AsyncServerExchangeHandler exchangeHandler; private volatile HttpRequest receivedRequest; ServerHttp1StreamHandler( final Http1StreamChannel outputChannel, final HttpProcessor httpProcessor, final Http1Config http1Config, final ConnectionReuseStrategy connectionReuseStrategy, final HandlerFactory exchangeHandlerFactory, final Callback exceptionCallback, final HttpCoreContext context) { this.outputChannel = outputChannel; this.internalDataChannel = new DataStreamChannel() { @Override public void requestOutput() { outputChannel.requestOutput(); } @Override public void endStream(final List trailers) throws IOException { responseState.set( MessageState.COMPLETE); outputChannel.complete(trailers); if (requestState.get() == MessageState.COMPLETE && !keepAlive) { outputChannel.close(); } } @Override public int write(final ByteBuffer src) throws IOException { return outputChannel.write(src); } @Override public void endStream() throws IOException { endStream(null); } }; this.responseChannel = new ResponseChannel() { @Override public void sendInformation(final HttpResponse response, final HttpContext httpContext) throws HttpException, IOException { commitInformation(response); } @Override public void sendResponse( final HttpResponse response, final EntityDetails responseEntityDetails, final HttpContext httpContext) throws HttpException, IOException { commitResponse(response, responseEntityDetails); } @Override public void pushPromise( final HttpRequest promise, final AsyncPushProducer pushProducer, final HttpContext httpContext) throws HttpException, IOException { commitPromise(); } @Override public void terminateExchange() { terminate(); } @Override public String toString() { return super.toString() + " " + ServerHttp1StreamHandler.this; } }; this.httpProcessor = httpProcessor; this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.connectionReuseStrategy = connectionReuseStrategy; this.exchangeHandlerFactory = exchangeHandlerFactory; this.exceptionCallback = exceptionCallback; this.context = context; this.requestState = new AtomicReference<>(MessageState.HEADERS); this.responseState = new AtomicReference<>(MessageState.IDLE); this.responseCommitted = new AtomicBoolean(); this.done = new AtomicBoolean(); this.keepAlive = true; } private void commitResponse( final HttpResponse response, final EntityDetails responseEntityDetails) throws HttpException, IOException { if (responseCommitted.compareAndSet(false, true)) { final ProtocolVersion transportVersion = response.getVersion(); if (transportVersion != null) { if (!transportVersion.lessEquals(http1Config.getVersion())) { throw new UnsupportedHttpVersionException(transportVersion); } context.setProtocolVersion(transportVersion); } final int status = response.getCode(); if (status < HttpStatus.SC_SUCCESS) { throw new HttpException("Invalid response: " + status); } context.setResponse(response); httpProcessor.process(response, responseEntityDetails, context); final boolean endStream = responseEntityDetails == null || receivedRequest != null && Method.HEAD.isSame(receivedRequest.getMethod()); if (!connectionReuseStrategy.keepAlive(receivedRequest, response, context)) { keepAlive = false; } if (endStream) { responseState.set(MessageState.COMPLETE); outputChannel.submit(response, true, FlushMode.IMMEDIATE); if (!keepAlive) { outputChannel.close(); } } else { outputChannel.submit(response, false, FlushMode.BUFFER); exchangeHandler.produce(internalDataChannel); if (responseState.compareAndSet(MessageState.IDLE, MessageState.BODY)) { outputChannel.requestOutput(); } } } else { throw new HttpException("Response already committed"); } } private void commitInformation(final HttpResponse response) throws IOException, HttpException { if (responseCommitted.get()) { throw new HttpException("Response already committed"); } final int status = response.getCode(); if (status < HttpStatus.SC_INFORMATIONAL || status >= HttpStatus.SC_SUCCESS) { throw new HttpException("Invalid intermediate response: " + status); } outputChannel.submit(response, true, FlushMode.IMMEDIATE); } private void commitPromise() throws HttpException { throw new HttpException("HTTP/1.1 does not support server push"); } private void terminate() { outputChannel.close(CloseMode.IMMEDIATE); } void activateChannel() throws IOException, HttpException { outputChannel.activate(); } boolean isResponseFinal() { return responseState.get() == MessageState.COMPLETE; } boolean keepAlive() { return keepAlive; } boolean isCompleted() { return requestState.get() == MessageState.COMPLETE && responseState.get() == MessageState.COMPLETE; } void terminateExchange(final HttpException ex) throws HttpException, IOException { if (done.get() || requestState.get() != MessageState.HEADERS) { throw new ProtocolException("Unexpected message head"); } receivedRequest = null; requestState.set(MessageState.COMPLETE); final HttpResponse response = new BasicHttpResponse(ServerSupport.toStatusCode(ex)); response.addHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); final AsyncResponseProducer responseProducer = new BasicResponseProducer(response, ServerSupport.toErrorMessage(ex)); exchangeHandler = new ImmediateResponseExchangeHandler(responseProducer); exchangeHandler.handleRequest(null, null, responseChannel, context); } void consumeHeader(final HttpRequest request, final EntityDetails requestEntityDetails) throws HttpException, IOException { if (done.get() || requestState.get() != MessageState.HEADERS) { throw new ProtocolException("Unexpected message head"); } receivedRequest = request; requestState.set(requestEntityDetails == null ? MessageState.COMPLETE : MessageState.BODY); try { final ProtocolVersion transportVersion = request.getVersion(); if (transportVersion != null && transportVersion.greaterEquals(HttpVersion.HTTP_2)) { throw new UnsupportedHttpVersionException(transportVersion); } context.setProtocolVersion(transportVersion != null ? transportVersion : http1Config.getVersion()); context.setRequest(request); httpProcessor.process(request, requestEntityDetails, context); AsyncServerExchangeHandler handler; try { handler = exchangeHandlerFactory.create(request, context); } catch (final MisdirectedRequestException ex) { handler = new ImmediateResponseExchangeHandler(HttpStatus.SC_MISDIRECTED_REQUEST, ex.getMessage()); } catch (final HttpException ex) { handler = new ImmediateResponseExchangeHandler(HttpStatus.SC_INTERNAL_SERVER_ERROR, ex.getMessage()); } if (handler == null) { handler = new ImmediateResponseExchangeHandler(HttpStatus.SC_NOT_FOUND, "Cannot handle request"); } exchangeHandler = handler; exchangeHandler.handleRequest(request, requestEntityDetails, responseChannel, context); } catch (final HttpException ex) { if (!responseCommitted.get()) { final HttpResponse response = new BasicHttpResponse(ServerSupport.toStatusCode(ex)); response.addHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); final AsyncResponseProducer responseProducer = new BasicResponseProducer(response, ServerSupport.toErrorMessage(ex)); exchangeHandler = new ImmediateResponseExchangeHandler(responseProducer); exchangeHandler.handleRequest(request, requestEntityDetails, responseChannel, context); } else { throw ex; } } } boolean isOutputReady() { switch (responseState.get()) { case BODY: return exchangeHandler.available() > 0; default: return false; } } void produceOutput() throws IOException { switch (responseState.get()) { case BODY: exchangeHandler.produce(internalDataChannel); break; } } void consumeData(final ByteBuffer src) throws HttpException, IOException { if (done.get() || requestState.get() != MessageState.BODY) { throw new ProtocolException("Unexpected message data"); } if (responseState.get() == MessageState.ACK) { outputChannel.requestOutput(); } exchangeHandler.consume(src); } void updateCapacity(final CapacityChannel capacityChannel) throws IOException { exchangeHandler.updateCapacity(capacityChannel); } void dataEnd(final List trailers) throws HttpException, IOException { if (done.get() || requestState.get() != MessageState.BODY) { throw new ProtocolException("Unexpected message data"); } requestState.set(MessageState.COMPLETE); if (responseState.get() == MessageState.COMPLETE && !keepAlive) { outputChannel.close(); } exchangeHandler.streamEnd(trailers); } void failed(final Exception cause) { if (!done.get() && exchangeHandler != null) { exchangeHandler.failed(cause); } else if (exceptionCallback != null) { exceptionCallback.execute(cause); } } @Override public void releaseResources() { if (done.compareAndSet(false, true)) { requestState.set(MessageState.COMPLETE); responseState.set(MessageState.COMPLETE); if (exchangeHandler != null) { exchangeHandler.releaseResources(); } } } void appendState(final StringBuilder buf) { buf.append("requestState=").append(requestState.get()) .append(", responseState=").append(responseState.get()) .append(", responseCommitted=").append(responseCommitted) .append(", keepAlive=").append(keepAlive) .append(", done=").append(done); } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append("["); appendState(buf); buf.append("]"); return buf.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/SessionInputBufferImpl.java0100644 0000000 0000000 00000023717 00000250600 027554 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; import org.apache.hc.core5.http.Chars; import org.apache.hc.core5.http.MessageConstraintException; import org.apache.hc.core5.http.nio.SessionInputBuffer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; class SessionInputBufferImpl extends ExpandableBuffer implements SessionInputBuffer { private final CharsetDecoder charDecoder; private final int lineBuffersize; private final int maxLineLen; private CharBuffer charbuffer; /** * Creates SessionInputBufferImpl instance. * * @param bufferSize input buffer size * @param lineBuffersize buffer size for line operations. Has effect only if * {@code charDecoder} is not {@code null}. * @param charDecoder charDecoder to be used for decoding HTTP protocol elements. * If {@code null} simple type cast will be used for byte to char conversion. * @param maxLineLen maximum line length. * * @since 4.4 */ public SessionInputBufferImpl( final int bufferSize, final int lineBuffersize, final int maxLineLen, final CharsetDecoder charDecoder) { super(bufferSize); this.lineBuffersize = Args.positive(lineBuffersize, "Line buffer size"); this.maxLineLen = Math.max(maxLineLen, 0); this.charDecoder = charDecoder; } /** * @since 4.3 */ public SessionInputBufferImpl( final int bufferSize, final int lineBuffersize, final int maxLineLen, final Charset charset) { this(bufferSize, lineBuffersize, maxLineLen, charset != null ? charset.newDecoder() : null); } /** * @since 4.3 */ public SessionInputBufferImpl( final int bufferSize, final int lineBuffersize, final int maxLineLen) { this(bufferSize, lineBuffersize, maxLineLen, (CharsetDecoder) null); } /** * @since 4.3 */ public SessionInputBufferImpl( final int bufferSize, final int lineBuffersize) { this(bufferSize, lineBuffersize, 0, (CharsetDecoder) null); } /** * @since 4.3 */ public SessionInputBufferImpl(final int bufferSize) { this(bufferSize, 256); } @Override public int length() { return super.length(); } @Override public boolean hasData() { return super.hasData(); } @Override public int capacity() { return super.capacity(); } public void put(final ByteBuffer src) { if (src != null && src.hasRemaining()) { setInputMode(); ensureAdjustedCapacity(buffer().position() + src.remaining()); buffer().put(src); } } @Override public int fill(final ReadableByteChannel channel) throws IOException { Args.notNull(channel, "Channel"); setInputMode(); if (!buffer().hasRemaining()) { expand(); } return channel.read(buffer()); } @Override public int read() { setOutputMode(); return buffer().get() & 0xff; } @Override public int read(final ByteBuffer dst, final int maxLen) { if (dst == null) { return 0; } setOutputMode(); final int len = Math.min(dst.remaining(), maxLen); final int chunk = Math.min(buffer().remaining(), len); if (buffer().remaining() > chunk) { final int oldLimit = buffer().limit(); final int newLimit = buffer().position() + chunk; buffer().limit(newLimit); dst.put(buffer()); buffer().limit(oldLimit); return len; } dst.put(buffer()); return chunk; } @Override public int read(final ByteBuffer dst) { if (dst == null) { return 0; } return read(dst, dst.remaining()); } @Override public int read(final WritableByteChannel dst, final int maxLen) throws IOException { if (dst == null) { return 0; } setOutputMode(); final int bytesRead; if (buffer().remaining() > maxLen) { final int oldLimit = buffer().limit(); final int newLimit = oldLimit - (buffer().remaining() - maxLen); buffer().limit(newLimit); bytesRead = dst.write(buffer()); buffer().limit(oldLimit); } else { bytesRead = dst.write(buffer()); } return bytesRead; } @Override public int read(final WritableByteChannel dst) throws IOException { if (dst == null) { return 0; } setOutputMode(); return dst.write(buffer()); } @Override public boolean readLine( final CharArrayBuffer lineBuffer, final boolean endOfStream) throws IOException { setOutputMode(); // See if there is LF char present in the buffer int pos = -1; for (int i = buffer().position(); i < buffer().limit(); i++) { final int b = buffer().get(i); if (b == Chars.LF) { pos = i + 1; break; } } if (this.maxLineLen > 0) { final int currentLen = (pos > 0 ? pos : buffer().limit()) - buffer().position(); if (currentLen >= this.maxLineLen) { throw new MessageConstraintException("Maximum line length limit exceeded"); } } if (pos == -1) { if (endOfStream && buffer().hasRemaining()) { // No more data. Get the rest pos = buffer().limit(); } else { // Either no complete line present in the buffer // or no more data is expected return false; } } final int origLimit = buffer().limit(); buffer().limit(pos); final int requiredCapacity = buffer().limit() - buffer().position(); // Ensure capacity of len assuming ASCII as the most likely charset lineBuffer.ensureCapacity(requiredCapacity); if (this.charDecoder == null) { if (buffer().hasArray()) { final byte[] b = buffer().array(); final int off = buffer().position(); final int len = buffer().remaining(); lineBuffer.append(b, buffer().arrayOffset() + off, len); buffer().position(off + len); } else { while (buffer().hasRemaining()) { lineBuffer.append((char) (buffer().get() & 0xff)); } } } else { if (this.charbuffer == null) { this.charbuffer = CharBuffer.allocate(this.lineBuffersize); } this.charDecoder.reset(); for (;;) { final CoderResult result = this.charDecoder.decode( buffer(), this.charbuffer, true); if (result.isError()) { result.throwException(); } if (result.isOverflow()) { this.charbuffer.flip(); lineBuffer.append( this.charbuffer.array(), this.charbuffer.arrayOffset() + this.charbuffer.position(), this.charbuffer.remaining()); this.charbuffer.clear(); } if (result.isUnderflow()) { break; } } // flush the decoder this.charDecoder.flush(this.charbuffer); this.charbuffer.flip(); // append the decoded content to the line buffer if (this.charbuffer.hasRemaining()) { lineBuffer.append( this.charbuffer.array(), this.charbuffer.arrayOffset() + this.charbuffer.position(), this.charbuffer.remaining()); } } buffer().limit(origLimit); // discard LF if found int l = lineBuffer.length(); if (l > 0) { if (lineBuffer.charAt(l - 1) == Chars.LF) { l--; lineBuffer.setLength(l); } // discard CR if found if (l > 0 && lineBuffer.charAt(l - 1) == Chars.CR) { l--; lineBuffer.setLength(l); } } return true; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/SessionOutputBufferImpl.java0100644 0000000 0000000 00000017450 00000250600 027752 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import org.apache.hc.core5.http.Chars; import org.apache.hc.core5.http.nio.SessionOutputBuffer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; import org.apache.hc.core5.util.TextUtils; class SessionOutputBufferImpl extends ExpandableBuffer implements SessionOutputBuffer { private static final byte[] CRLF = new byte[] {Chars.CR, Chars.LF}; private final CharsetEncoder charEncoder; private final int lineBufferSize; private CharBuffer charbuffer; /** * Creates SessionOutputBufferImpl instance. * * @param bufferSize input buffer size * @param lineBufferSize buffer size for line operations. Has effect only if * {@code charEncoder} is not {@code null}. * @param charEncoder charEncoder to be used for encoding HTTP protocol elements. * If {@code null} simple type cast will be used for char to byte conversion. * * @since 4.3 */ public SessionOutputBufferImpl( final int bufferSize, final int lineBufferSize, final CharsetEncoder charEncoder) { super(bufferSize); this.lineBufferSize = Args.positive(lineBufferSize, "Line buffer size"); this.charEncoder = charEncoder; } /** * @since 4.3 */ public SessionOutputBufferImpl( final int bufferSize, final int lineBufferSize, final Charset charset) { this(bufferSize, lineBufferSize, charset != null ? charset.newEncoder() : null); } /** * @since 4.3 */ public SessionOutputBufferImpl( final int bufferSize, final int lineBufferSize) { this(bufferSize, lineBufferSize, (CharsetEncoder) null); } /** * @since 4.3 */ public SessionOutputBufferImpl(final int bufferSize) { this(bufferSize, 256); } @Override public int length() { return super.length(); } @Override public boolean hasData() { return super.hasData(); } @Override public int capacity() { return super.capacity(); } @Override public int flush(final WritableByteChannel channel) throws IOException { Args.notNull(channel, "Channel"); setOutputMode(); return channel.write(buffer()); } @Override public void write(final ByteBuffer src) { if (src == null) { return; } setInputMode(); ensureAdjustedCapacity(buffer().position() + src.remaining()); buffer().put(src); } @Override public void write(final ReadableByteChannel src) throws IOException { if (src == null) { return; } setInputMode(); src.read(buffer()); } private void write(final byte[] b) { if (b == null) { return; } setInputMode(); final int off = 0; final int len = b.length; final int requiredCapacity = buffer().position() + len; ensureAdjustedCapacity(requiredCapacity); buffer().put(b, off, len); } private void writeCRLF() { write(CRLF); } @Override public void writeLine(final CharArrayBuffer lineBuffer) throws CharacterCodingException { if (lineBuffer == null) { return; } setInputMode(); // Do not bother if the buffer is empty if (lineBuffer.length() > 0 ) { if (this.charEncoder == null) { final int requiredCapacity = buffer().position() + lineBuffer.length(); ensureCapacity(requiredCapacity); if (buffer().hasArray()) { final byte[] b = buffer().array(); final int len = lineBuffer.length(); final int off = buffer().position(); final int arrayOffset = buffer().arrayOffset(); for (int i = 0; i < len; i++) { final int c = lineBuffer.charAt(i); b[arrayOffset + off + i] = TextUtils.castAsByte(c); } buffer().position(off + len); } else { for (int i = 0; i < lineBuffer.length(); i++) { final int c = lineBuffer.charAt(i); buffer().put(TextUtils.castAsByte(c)); } } } else { if (this.charbuffer == null) { this.charbuffer = CharBuffer.allocate(this.lineBufferSize); } this.charEncoder.reset(); // transfer the string in small chunks int remaining = lineBuffer.length(); int offset = 0; while (remaining > 0) { int l = this.charbuffer.remaining(); boolean eol = false; if (remaining <= l) { l = remaining; // terminate the encoding process eol = true; } this.charbuffer.put(lineBuffer.array(), offset, l); this.charbuffer.flip(); boolean retry = true; while (retry) { final CoderResult result = this.charEncoder.encode(this.charbuffer, buffer(), eol); if (result.isError()) { result.throwException(); } if (result.isOverflow()) { expand(); } retry = !result.isUnderflow(); } this.charbuffer.compact(); offset += l; remaining -= l; } // flush the encoder boolean retry = true; while (retry) { final CoderResult result = this.charEncoder.flush(buffer()); if (result.isError()) { result.throwException(); } if (result.isOverflow()) { expand(); } retry = !result.isUnderflow(); } } } writeCRLF(); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/package-info.java0100644 0000000 0000000 00000002473 00000250600 025455 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Default implementation of HTTP/1.1 transport based on * the asynchronous (non-blocking) I/O model. */ package org.apache.hc.core5.http.impl.nio; httpcore5/src/main/java/org/apache/hc/core5/http/impl/routing/0040755 0000000 0000000 00000000000 00000250600 023165 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/impl/routing/PathPatternMatcher.java0100644 0000000 0000000 00000004445 00000250600 027572 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.routing; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; /** * URI path component pattern matcher. *

* Patterns may have three formats: *

*
    *
  • {@code *}
  • *
  • {@code *}
  • *
  • {@code *}
  • *
* * @since 5.3 */ @Contract(threading = ThreadingBehavior.STATELESS) public final class PathPatternMatcher { public static final PathPatternMatcher INSTANCE = new PathPatternMatcher(); public boolean match(final String pattern, final String path) { if (pattern.equals("*") || pattern.equals(path)) { return true; } return pattern.endsWith("*") && path.startsWith(pattern.substring(0, pattern.length() - 1)) || pattern.startsWith("*") && path.endsWith(pattern.substring(1)); } public boolean isBetter(final String pattern, final String bestMatch) { return bestMatch == null || bestMatch.length() < pattern.length() || bestMatch.length() == pattern.length() && pattern.endsWith("*"); } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/routing/PathRoute.java0100644 0000000 0000000 00000004054 00000250600 025743 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.routing; import java.util.Objects; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.util.Args; @Internal public final class PathRoute { public final T pattern; public final H handler; public PathRoute(final T pattern, final H handler) { this.pattern = Args.notNull(pattern, "Pattern"); this.handler = Args.notNull(handler, "Handler"); } @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final PathRoute other = (PathRoute) o; return Objects.equals(pattern, other.pattern); } @Override public int hashCode() { return pattern.hashCode(); } @Override public String toString() { return pattern + " -> " + handler; } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/routing/RequestRouter.java0100644 0000000 0000000 00000025600 00000250600 026661 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.routing; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collectors; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestMapper; import org.apache.hc.core5.http.MisdirectedRequestException; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.UriPatternType; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.util.Args; /** * Request mapper that can route requests based on their properties to a specific request handler. * * @param request handler type. * @since 5.3 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class RequestRouter implements HttpRequestMapper { @Internal public final static class Entry { public final URIAuthority uriAuthority; public final PathRoute route; public Entry(final URIAuthority uriAuthority, final String pathPattern, final T handler) { this.uriAuthority = uriAuthority; this.route = new PathRoute<>(pathPattern, handler); } public Entry(final String hostname, final String pathPattern, final T handler) { this(new URIAuthority(hostname), pathPattern, handler); } public Entry(final String pathPattern, final T handler) { this((URIAuthority) null, pathPattern, handler); } @Override public String toString() { return uriAuthority + "/" + route; } } static final class SingleAuthorityResolver implements Function { private final URIAuthority singleAuthority; private final T router; SingleAuthorityResolver(final URIAuthority singleAuthority, final T router) { this.singleAuthority = Args.notNull(singleAuthority, "singleAuthority"); this.router = router; } @Override public T apply(final URIAuthority authority) { return singleAuthority.equals(authority) ? router : null; } @Override public String toString() { return singleAuthority + " " + router; } } static final Function noAuthorityResolver() { return u -> null; } @Internal public static RequestRouter create(final URIAuthority primaryAuthority, final UriPatternType patternType, final List> handlerEntries, final BiFunction authorityResolver, final HttpRequestMapper downstream) { final Map> authorityMap = handlerEntries.stream() .collect(Collectors.groupingBy( e -> e.uriAuthority != null ? e.uriAuthority : primaryAuthority != null ? primaryAuthority : LOCAL_AUTHORITY, Collectors.mapping(e -> e.route, Collectors.collectingAndThen(Collectors.toList(), e -> { switch (patternType) { case URI_PATTERN: return UriPathRouter.bestMatch(e); case URI_PATTERN_IN_ORDER: return UriPathRouter.ordered(e); case REGEX: return UriPathRouter.regEx(e); default: throw new IllegalStateException("Unexpected pattern type: " + patternType); } })))); final Function> authorityFunction; if (authorityMap.isEmpty()) { authorityFunction = noAuthorityResolver(); } else if (authorityMap.size() == 1) { final Map.Entry> entry = authorityMap.entrySet().iterator().next(); authorityFunction = new SingleAuthorityResolver<>(entry.getKey(), entry.getValue()); } else { authorityFunction = authorityMap::get; } return new RequestRouter<>(authorityFunction, authorityResolver, downstream); } public static Builder builder(final UriPatternType patternType) { return new Builder<>(patternType); } public static Builder builder() { return new Builder<>(UriPatternType.URI_PATTERN); } public static final URIAuthority LOCAL_AUTHORITY = new URIAuthority("localhost"); public final static BiFunction LOCAL_AUTHORITY_RESOLVER = (scheme, authority) -> LOCAL_AUTHORITY; public final static BiFunction IGNORE_PORT_AUTHORITY_RESOLVER = (scheme, authority) -> authority != null && authority.getPort() != -1 ? new URIAuthority(authority.getHostName(), -1) : authority; private final Function> authorityRouter; private final BiFunction authorityResolver; private final HttpRequestMapper downstream; RequestRouter(final Function> authorityRouter, final BiFunction authorityResolver, final HttpRequestMapper downstream) { this.authorityRouter = authorityRouter; this.authorityResolver = authorityResolver; this.downstream = downstream; } @Override public T resolve(final HttpRequest request, final HttpContext context) throws HttpException { final URIAuthority authority = authorityResolver != null ? authorityResolver.apply(request.getScheme(), request.getAuthority()) : request.getAuthority(); final Function pathRouter = authority != null ? authorityRouter.apply(authority) : null; if (pathRouter == null) { if (downstream != null) { return downstream.resolve(request, context); } throw new MisdirectedRequestException("Not authoritative"); } String path = request.getPath(); final int i = path.indexOf('?'); if (i != -1) { path = path.substring(0, i); } return pathRouter.apply(path); } public static class Builder { private final UriPatternType patternType; private final List> handlerEntries; private BiFunction authorityResolver; private HttpRequestMapper downstream; Builder(final UriPatternType patternType) { this.patternType = patternType != null ? patternType : UriPatternType.URI_PATTERN; this.handlerEntries = new ArrayList<>(); } /** * Adds a route with given authority and path pattern. Requests with the same authority and matching the path pattern * will be routed for execution to the handler. */ public Builder addRoute(final URIAuthority authority, final String pathPattern, final T handler) { Args.notNull(authority, "URI authority"); Args.notBlank(pathPattern, "URI path pattern"); Args.notNull(handler, "Handler"); this.handlerEntries.add(new Entry<>(authority, pathPattern, handler)); return this; } /** * Adds a route with given hostname and path pattern. Requests with the same hostname and matching the path pattern * will be routed for execution to the handler. */ public Builder addRoute(final String hostname, final String pathPattern, final T handler) { Args.notBlank(hostname, "Hostname"); Args.notBlank(pathPattern, "URI path pattern"); Args.notNull(handler, "Handler"); this.handlerEntries.add(new Entry<>(hostname, pathPattern, handler)); return this; } /** * Sets custom {@link URIAuthority} resolution {@link Function} that can be used to normalize or re-write * the authority specified in incoming requests prior to request routing. The function can return * a new {@link URIAuthority} instance representing an identity of the service authoritative to handle * the request or {@code null} if an authoritative service cannot be found or is unknown. */ public Builder resolveAuthority(final BiFunction authorityResolver) { this.authorityResolver = authorityResolver; return this; } /** * Sets a downstream request mapper that can be used as a fallback in case no authoritative service can be found * to handle an incoming request. Using this method request mappers can be linked to form a chain of responsibility, * with each link representing a different authority. */ public Builder downstream(final HttpRequestMapper downstream) { this.downstream = downstream; return this; } public RequestRouter build() { return RequestRouter.create(null, patternType, handlerEntries, authorityResolver, downstream); } } } httpcore5/src/main/java/org/apache/hc/core5/http/impl/routing/UriPathRouter.java0100644 0000000 0000000 00000015363 00000250600 026612 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.routing; import java.util.Collections; import java.util.List; import java.util.function.BiFunction; import java.util.function.Function; import java.util.regex.Pattern; import java.util.stream.Collectors; final class UriPathRouter implements Function { private final BiFunction>, T> pathRouter; private final List> routes; UriPathRouter(final Function compiler, final BiFunction>, T> pathRouter, final List> routes) { this.pathRouter = pathRouter; this.routes = Collections.unmodifiableList(routes.stream() .map(e -> new PathRoute<>(compiler.apply(e.pattern), e.handler)) .collect(Collectors.toList())); } @Override public T apply(final String path) { return pathRouter.apply(path, routes); } @Override public String toString() { return routes.toString(); } static UriPathRouter bestMatch(final List> routes) { return new UriPathRouter<>(Function.identity(), BestMatcher.getInstance(), routes); } static UriPathRouter ordered(final List> routes) { return new UriPathRouter<>(Function.identity(), OrderedMatcher.getInstance(), routes); } static UriPathRouter regEx(final List> routes) { return new UriPathRouter<>(Pattern::compile, RegexMatcher.getInstance(), routes); } private static final PathPatternMatcher PATH_PATTERN_MATCHER = PathPatternMatcher.INSTANCE; /** * Finds a match for the given path from a collection of URI patterns. *

* Patterns may have three formats: *

*
    *
  • {@code *}
  • *
  • {@code *}
  • *
  • {@code *}
  • *
*

* This class has no instance state. *

*/ final static class BestMatcher implements BiFunction>, T> { @SuppressWarnings("rawtypes") // raw by design private static final BestMatcher INSTANCE = new BestMatcher(); @SuppressWarnings({ "cast", "unchecked" }) // cast to call site static BestMatcher getInstance() { return (BestMatcher) INSTANCE; } private BestMatcher() { // singleton instance only } @Override public T apply(final String path, final List> routes) { PathRoute bestMatch = null; for (final PathRoute route : routes) { if (route.pattern.equals(path)) { return route.handler; } if (PATH_PATTERN_MATCHER.match(route.pattern, path)) { // we have a match. is it any better? if (bestMatch == null || PATH_PATTERN_MATCHER.isBetter(route.pattern, bestMatch.pattern)) { bestMatch = route; } } } return bestMatch != null ? bestMatch.handler : null; } } /** * Finds a match for the given path from an ordered collection of URI patterns. *

* Patterns may have three formats: *

*
    *
  • {@code *}
  • *
  • {@code *}
  • *
  • {@code *}
  • *
*

* This class has no instance state. *

*/ final static class OrderedMatcher implements BiFunction>, T> { @SuppressWarnings("rawtypes") // raw by design private static final OrderedMatcher INSTANCE = new OrderedMatcher(); @SuppressWarnings({ "cast", "unchecked" }) // cast to call site static OrderedMatcher getInstance() { return (OrderedMatcher) INSTANCE; } private OrderedMatcher() { // singleton instance only } @Override public T apply(final String path, final List> routes) { for (final PathRoute route : routes) { final String pattern = route.pattern; if (path.equals(pattern)) { return route.handler; } if (PATH_PATTERN_MATCHER.match(pattern, path)) { return route.handler; } } return null; } } /** * Finds a match for the given path from a collection of regular expressions. *

* This class has no instance state. *

*/ final static class RegexMatcher implements BiFunction>, T> { @SuppressWarnings("rawtypes") // raw by design private static final RegexMatcher INSTANCE = new RegexMatcher(); @SuppressWarnings({ "cast", "unchecked" }) // cast to call site static RegexMatcher getInstance() { return (RegexMatcher) INSTANCE; } private RegexMatcher() { // singleton instance only } @Override public T apply(final String path, final List> routes) { for (final PathRoute route : routes) { final Pattern pattern = route.pattern; if (pattern.matcher(path).matches()) { return route.handler; } } return null; } } } httpcore5/src/main/java/org/apache/hc/core5/http/io/0040755 0000000 0000000 00000000000 00000250600 021144 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/io/BHttpConnection.java0100644 0000000 0000000 00000006101 00000250600 025043 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import java.io.IOException; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.util.Timeout; /** * Abstract blocking HTTP connection interface. * * @since 5.0 */ public interface BHttpConnection extends HttpConnection { /** * Checks if input data is available from the connection. May wait for * the specified time until some data becomes available. Note that some * implementations may completely ignore the timeout parameter. * * @param timeout the maximum time to wait for data * @return true if data is available; false if there was no data available * even after waiting for {@code timeout}. * @throws IOException if an error happens on the connection */ boolean isDataAvailable(Timeout timeout) throws IOException; /** * Checks whether this connection has gone down. * Network connections may get closed during some time of inactivity * for several reasons. The next time a read is attempted on such a * connection it will throw an IOException. * This method tries to alleviate this inconvenience by trying to * find out if a connection is still usable. Implementations may do * that by attempting a read with a very small timeout. Thus this * method may block for a small amount of time before returning a result. * It is therefore an expensive operation. * * @return {@code true} if attempts to use this connection are likely * to fail and this connection should be closed, * or {@code false} if they are likely to succeed * @throws IOException in case of an I/O error. */ boolean isStale() throws IOException; /** * Writes out all pending buffered data over the open connection. * * @throws java.io.IOException in case of an I/O error */ void flush() throws IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/io/EofSensorInputStream.java0100644 0000000 0000000 00000022261 00000250600 026106 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import java.io.IOException; import java.io.InputStream; import org.apache.hc.core5.util.Args; /** * A stream wrapper that triggers actions on {@link #close close()} and EOF. * Primarily used to auto-release an underlying managed connection when the response * body is consumed or no longer needed. * * @see EofSensorWatcher * * @since 4.0 */ // don't use FilterInputStream as the base class, we'd have to // override markSupported(), mark(), and reset() to disable them public class EofSensorInputStream extends InputStream { /** * The wrapped input stream, while accessible. * The value changes to {@code null} when the wrapped stream * becomes inaccessible. */ private InputStream wrappedStream; /** * Indicates whether this stream itself is closed. * If it isn't, but {@link #wrappedStream wrappedStream} * is {@code null}, we're running in EOF mode. * All read operations will indicate EOF without accessing * the underlying stream. After closing this stream, read * operations will trigger an {@link IOException IOException}. * * @see #isReadAllowed isReadAllowed */ private boolean selfClosed; /** The watcher to be notified, if any. */ private final EofSensorWatcher eofWatcher; /** * Creates a new EOF sensor. * If no watcher is passed, the underlying stream will simply be * closed when EOF is detected or {@link #close close} is called. * Otherwise, the watcher decides whether the underlying stream * should be closed before detaching from it. * * @param in the wrapped stream * @param watcher the watcher for events, or {@code null} for * auto-close behavior without notification */ public EofSensorInputStream(final InputStream in, final EofSensorWatcher watcher) { Args.notNull(in, "Wrapped stream"); wrappedStream = in; selfClosed = false; eofWatcher = watcher; } boolean isSelfClosed() { return selfClosed; } InputStream getWrappedStream() { return wrappedStream; } /** * Checks whether the underlying stream can be read from. * * @return {@code true} if the underlying stream is accessible, * {@code false} if this stream is in EOF mode and * detached from the underlying stream * * @throws IOException if this stream is already closed */ private boolean isReadAllowed() throws IOException { if (selfClosed) { throw new IOException("Attempted read on closed stream."); } return wrappedStream != null; } @Override public int read() throws IOException { int b = -1; if (isReadAllowed()) { try { b = wrappedStream.read(); checkEOF(b); } catch (final IOException ex) { checkAbort(); throw ex; } } return b; } @Override public int read(final byte[] b, final int off, final int len) throws IOException { if (len == 0) { return 0; } int readLen = -1; if (isReadAllowed()) { try { readLen = wrappedStream.read(b, off, len); checkEOF(readLen); } catch (final IOException ex) { checkAbort(); throw ex; } } return readLen; } @Override public int read(final byte[] b) throws IOException { return read(b, 0, b.length); } @Override public int available() throws IOException { int a = 0; // not -1 if (isReadAllowed()) { try { a = wrappedStream.available(); // no checkEOF() here, available() can't trigger EOF } catch (final IOException ex) { checkAbort(); throw ex; } } return a; } @Override public void close() throws IOException { // tolerate multiple calls to close() selfClosed = true; checkClose(); } /** * Detects EOF and notifies the watcher. * This method should only be called while the underlying stream is * still accessible. Use {@link #isReadAllowed isReadAllowed} to * check that condition. *

* If EOF is detected, the watcher will be notified and this stream * is detached from the underlying stream. This prevents multiple * notifications from this stream. *

* * @param eof the result of the calling read operation. * A negative value indicates that EOF is reached. * * @throws IOException * in case of an IO problem on closing the underlying stream */ private void checkEOF(final int eof) throws IOException { final InputStream toCheckStream = wrappedStream; if (toCheckStream != null && eof < 0) { try { boolean scws = true; // should close wrapped stream? if (eofWatcher != null) { scws = eofWatcher.eofDetected(toCheckStream); } if (scws) { toCheckStream.close(); } } finally { wrappedStream = null; } } } /** * Detects stream close and notifies the watcher. * There's not much to detect since this is called by {@link #close close}. * The watcher will only be notified if this stream is closed * for the first time and before EOF has been detected. * This stream will be detached from the underlying stream to prevent * multiple notifications to the watcher. * * @throws IOException * in case of an IO problem on closing the underlying stream */ private void checkClose() throws IOException { final InputStream toCloseStream = wrappedStream; if (toCloseStream != null) { try { boolean scws = true; // should close wrapped stream? if (eofWatcher != null) { scws = eofWatcher.streamClosed(toCloseStream); } if (scws) { toCloseStream.close(); } } finally { wrappedStream = null; } } } /** * Detects stream abort and notifies the watcher. * There's not much to detect since this is called by {@link #abort()}. * The watcher will only be notified if this stream is aborted * for the first time and before EOF has been detected or the * stream has been {@link #close closed} gracefully. * This stream will be detached from the underlying stream to prevent * multiple notifications to the watcher. * * @throws IOException * in case of an IO problem on closing the underlying stream */ private void checkAbort() throws IOException { final InputStream toAbortStream = wrappedStream; if (toAbortStream != null) { try { boolean scws = true; // should close wrapped stream? if (eofWatcher != null) { scws = eofWatcher.streamAbort(toAbortStream); } if (scws) { toAbortStream.close(); } } finally { wrappedStream = null; } } } /** * Aborts this stream. * This is a special version of {@link #close close()} which prevents * re-use of the underlying connection, if any. Calling this method * indicates that there should be no attempt to read until the end of * the stream. * * @throws IOException * in case of an IO problem on closing the underlying stream */ public void abort() throws IOException { // tolerate multiple calls selfClosed = true; checkAbort(); } } httpcore5/src/main/java/org/apache/hc/core5/http/io/EofSensorWatcher.java0100644 0000000 0000000 00000007244 00000250600 025234 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import java.io.IOException; import java.io.InputStream; /** * A watcher for {@link EofSensorInputStream}. Each stream will notify its * watcher at most once. * * @since 4.0 */ public interface EofSensorWatcher { /** * Indicates that EOF is detected. * * @param wrapped the underlying stream which has reached EOF * * @return {@code true} if {@code wrapped} should be closed, * {@code false} if it should be left alone * * @throws IOException * in case of an IO problem, for example if the watcher itself * closes the underlying stream. The caller will leave the * wrapped stream alone, as if {@code false} was returned. */ boolean eofDetected(InputStream wrapped) throws IOException; /** * Indicates that the {@link EofSensorInputStream stream} is closed. * This method will be called only if EOF was not detected * before closing. Otherwise, {@link #eofDetected eofDetected} is called. * * @param wrapped the underlying stream which has not reached EOF * * @return {@code true} if {@code wrapped} should be closed, * {@code false} if it should be left alone * * @throws IOException * in case of an IO problem, for example if the watcher itself * closes the underlying stream. The caller will leave the * wrapped stream alone, as if {@code false} was returned. */ boolean streamClosed(InputStream wrapped) throws IOException; /** * Indicates that the {@link EofSensorInputStream stream} is aborted. * This method will be called only if EOF was not detected * before aborting. Otherwise, {@link #eofDetected eofDetected} is called. *

* This method will also be invoked when an input operation causes an * IOException to be thrown to make sure the input stream gets shut down. *

* * @param wrapped the underlying stream which has not reached EOF * * @return {@code true} if {@code wrapped} should be closed, * {@code false} if it should be left alone * * @throws IOException * in case of an IO problem, for example if the watcher itself * closes the underlying stream. The caller will leave the * wrapped stream alone, as if {@code false} was returned. */ boolean streamAbort(InputStream wrapped) throws IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpClientConnection.java0100644 0000000 0000000 00000010001 00000250600 026072 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import java.io.IOException; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpException; /** * A client-side HTTP connection, which can be used for sending * requests and receiving responses. * * @since 4.0 */ public interface HttpClientConnection extends BHttpConnection { /** * Checks whether this connection is in a consistent state. * * @return {@code false} if the connection is known to be * in a inconsistent state and cannot be re-used. * * @see #terminateRequest(ClassicHttpRequest) * * @since 5.0 */ boolean isConsistent(); /** * Sends the request line and all headers over the connection. * @param request the request whose headers to send. * @throws HttpException in case of HTTP protocol violation * @throws IOException in case of an I/O error */ void sendRequestHeader(ClassicHttpRequest request) throws HttpException, IOException; /** * Terminates request prematurely potentially leaving * the connection in a inconsistent state. * * @param request the request to be terminated prematurely. * @throws HttpException * @throws IOException * * @see #isConsistent() * * @since 5.0 */ void terminateRequest(ClassicHttpRequest request) throws HttpException, IOException; /** * Sends the request entity over the connection. * @param request the request whose entity to send. * @throws HttpException in case of HTTP protocol violation * @throws IOException in case of an I/O error */ void sendRequestEntity(ClassicHttpRequest request) throws HttpException, IOException; /** * Receives the request line and headers of the next response available from * this connection. The caller should examine the HttpResponse object to * find out if it should try to receive a response entity as well. * * @return a new HttpResponse object with status line and headers * initialized or {@code null} if the connection has been closed * by the opposite endpoint. * @throws HttpException in case of HTTP protocol violation * @throws IOException in case of an I/O error */ ClassicHttpResponse receiveResponseHeader() throws HttpException, IOException; /** * Receives the next response entity available from this connection and * attaches it to an existing HttpResponse object. * * @param response the response to attach the entity to * @throws HttpException in case of HTTP protocol violation * @throws IOException in case of an I/O error */ void receiveResponseEntity(ClassicHttpResponse response) throws HttpException, IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpClientResponseHandler.java0100644 0000000 0000000 00000004027 00000250600 027102 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import java.io.IOException; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpException; /** * Handler that encapsulates the process of generating a response object * from a {@link ClassicHttpResponse}. * * @param the type of the response. * @since 4.0 */ @FunctionalInterface public interface HttpClientResponseHandler { /** * Processes an {@link ClassicHttpResponse} and returns some value * corresponding to that response. * * @param response The response to process * @return A value determined by the response * * @throws IOException in case of a problem or the connection was aborted * @throws HttpException in case of an HTTP protocol violation. */ T handleResponse(ClassicHttpResponse response) throws HttpException, IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpConnectionFactory.java0100644 0000000 0000000 00000004552 00000250600 026301 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import java.io.IOException; import java.net.Socket; import javax.net.ssl.SSLSocket; import org.apache.hc.core5.http.HttpConnection; /** * Factory for {@link HttpConnection} instances. * * @param The type of {@link HttpConnection}. * @since 4.3 */ public interface HttpConnectionFactory { /** * Creates TLS connection with a {@link SSLSocket} layered over a plain {@link Socket}. * * @param socket the plain socket SSL socket has been layered over. * @return a new HttpConnection. * @throws IOException in case of an I/O error. */ T createConnection(Socket socket) throws IOException; /** * Creates TLS connection with a {@link SSLSocket} layered over a plain {@link Socket}. * * @param sslSocket the SSL socket. May be {@code null}. * @param socket the plain socket SSL socket has been layered over. * @return a new HttpConnection. * @throws IOException in case of an I/O error. * @since 5.3 */ default T createConnection(SSLSocket sslSocket, Socket socket) throws IOException { return createConnection(sslSocket != null ? sslSocket : socket); } } httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpFilterChain.java0100644 0000000 0000000 00000006257 00000250600 025046 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.protocol.HttpContext; /** * HttpFilterChain represents a single element in the server side request processing chain. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.STATELESS) public interface HttpFilterChain { /** * Response trigger that can be used to generate the final HTTP response * and terminate HTTP request processing. */ interface ResponseTrigger { /** * Sends an intermediate informational HTTP response to the client. * * @param response an intermediate (1xx) HTTP response. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void sendInformation(ClassicHttpResponse response) throws HttpException, IOException; /** * Sends a final HTTP response to the client. * * @param response a final (non 1xx) HTTP response. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void submitResponse(ClassicHttpResponse response) throws HttpException, IOException; } /** * Proceeds to the next element in the request processing chain. * * @param request the actual request. * @param responseTrigger the response trigger. * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void proceed( ClassicHttpRequest request, ResponseTrigger responseTrigger, HttpContext context) throws HttpException, IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpFilterHandler.java0100644 0000000 0000000 00000005172 00000250600 025374 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.protocol.HttpContext; /** * HttpFilterHandler represents a routine for handling all incoming requests * in the server side request processing chain. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.STATELESS) public interface HttpFilterHandler { /** * Processes the incoming HTTP request and if processing has been completed * submits a final response to the client. The handler must not use * the response trigger after passing control to the next filter with the * {@link HttpFilterChain#proceed(ClassicHttpRequest, HttpFilterChain.ResponseTrigger, HttpContext)} * method. * * @param request the actual request. * @param responseTrigger the response trigger. * @param context the actual execution context. * @param chain the next element in the request processing chain. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void handle( ClassicHttpRequest request, HttpFilterChain.ResponseTrigger responseTrigger, HttpContext context, HttpFilterChain chain) throws HttpException, IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageParser.java0100644 0000000 0000000 00000004106 00000250600 025406 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import java.io.IOException; import java.io.InputStream; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.MessageHeaders; /** * Message parser intended to build HTTP message head from an input stream. * * @param The type of {@link MessageHeaders}. * * @since 4.0 */ public interface HttpMessageParser { /** * Generates an instance of {@link MessageHeaders} from the given input stream.. * * @param buffer Session input buffer * @param inputStream Input stream * @return HTTP message head or {@code null} if the input stream has been * closed by the opposite endpoint. * @throws IOException in case of an I/O error * @throws HttpException in case of HTTP protocol violation */ T parse(SessionInputBuffer buffer, InputStream inputStream) throws IOException, HttpException; } httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageParserFactory.java0100644 0000000 0000000 00000003327 00000250600 026742 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import org.apache.hc.core5.http.MessageHeaders; import org.apache.hc.core5.http.config.Http1Config; /** * Factory for {@link HttpMessageParser} instances. * * @param The type of {@link MessageHeaders}. * @since 4.3 */ public interface HttpMessageParserFactory { /** * @deprecated Use {@link #create()} */ @Deprecated HttpMessageParser create(Http1Config http1Config); default HttpMessageParser create() { return create(Http1Config.DEFAULT); } } httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageWriter.java0100644 0000000 0000000 00000003743 00000250600 025434 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import java.io.IOException; import java.io.OutputStream; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.MessageHeaders; /** * Message writer intended to serialize HTTP message head to an output stream. * * @param The type of {@link MessageHeaders}. * @since 4.0 */ public interface HttpMessageWriter { /** * Serializes an instance of {@link MessageHeaders} to the given output stream. * * @param message HTTP message head * @param buffer session output buffer * @throws IOException in case of an I/O error * @throws HttpException in case of HTTP protocol violation */ void write(T message, SessionOutputBuffer buffer, OutputStream outputStream) throws IOException, HttpException; } httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageWriterFactory.java0100644 0000000 0000000 00000002744 00000250600 026764 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import org.apache.hc.core5.http.MessageHeaders; /** * Factory for {@link HttpMessageWriter} instances. * * @param The type of {@link MessageHeaders}. * @since 4.3 */ public interface HttpMessageWriterFactory { HttpMessageWriter create(); } httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpRequestHandler.java0100644 0000000 0000000 00000004712 00000250600 025576 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import java.io.IOException; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.protocol.HttpContext; /** * HttpRequestHandler represents a routine for processing of a specific group * of HTTP requests. Protocol handlers are designed to take care of protocol * specific aspects, whereas individual request handlers are expected to take * care of application specific HTTP processing. The main purpose of a request * handler is to generate a response object with a content entity to be sent * back to the client in response to the given request * * @since 4.0 */ public interface HttpRequestHandler { /** * Handles the request and produces a response to be sent back to * the client. * * @param request the HTTP request. * @param response the HTTP response. * @param context the HTTP execution context. * @throws IOException in case of an I/O error. * @throws HttpException in case of HTTP protocol violation or a processing * problem. */ void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpResponseInformationCallback.java0100644 0000000 0000000 00000003170 00000250600 030266 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.protocol.HttpContext; /** * Informational (1xx) HTTP response callback. * * @since 5.0 */ public interface HttpResponseInformationCallback { void execute(HttpResponse response, HttpConnection connection, HttpContext context) throws HttpException; } httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpServerConnection.java0100644 0000000 0000000 00000006431 00000250600 026136 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import java.io.IOException; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpException; /** * A server-side HTTP connection, which can be used for receiving * requests and sending responses. * * @since 4.0 */ public interface HttpServerConnection extends BHttpConnection { /** * Receives the request line and all headers available from this connection. * The caller should examine the returned request and decide if to receive a * request entity as well. * * @return a new HttpRequest object whose request line and headers are * initialized or {@code null} if the connection has been closed * by the opposite endpoint. * @throws HttpException in case of HTTP protocol violation * @throws IOException in case of an I/O error */ ClassicHttpRequest receiveRequestHeader() throws HttpException, IOException; /** * Receives the next request entity available from this connection and attaches it to * an existing request. * @param request the request to attach the entity to. * @throws HttpException in case of HTTP protocol violation * @throws IOException in case of an I/O error */ void receiveRequestEntity(ClassicHttpRequest request) throws HttpException, IOException; /** * Sends the response line and headers of a response over this connection. * @param response the response whose headers to send. * @throws HttpException in case of HTTP protocol violation * @throws IOException in case of an I/O error */ void sendResponseHeader(ClassicHttpResponse response) throws HttpException, IOException; /** * Sends the response entity of a response over this connection. * @param response the response whose entity to send. * @throws HttpException in case of HTTP protocol violation * @throws IOException in case of an I/O error */ void sendResponseEntity(ClassicHttpResponse response) throws HttpException, IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpServerRequestHandler.java0100644 0000000 0000000 00000007065 00000250600 026771 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.protocol.HttpContext; /** * HttpServerRequestHandler represents a routine for processing of a specific group * of HTTP requests. Request execution filters are designed to take care of protocol * specific aspects, whereas individual request handlers are expected to take * care of application specific HTTP processing. The main purpose of a request * handler is to generate a response object with a content entity to be sent * back to the client in response to the given request. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.STATELESS) public interface HttpServerRequestHandler { /** * Response trigger that can be used to submit a final HTTP response * and terminate HTTP request processing. */ interface ResponseTrigger { /** * Sends an intermediate informational HTTP response to the client. * * @param response the intermediate (1xx) HTTP response * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void sendInformation(ClassicHttpResponse response) throws HttpException, IOException; /** * Sends a final HTTP response to the client. * * @param response the final (non 1xx) HTTP response * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void submitResponse(ClassicHttpResponse response) throws HttpException, IOException; } /** * Handles the request and submits a final response to be sent back to the client. * * @param request the actual request. * @param responseTrigger the response trigger. * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void handle( ClassicHttpRequest request, ResponseTrigger responseTrigger, HttpContext context) throws HttpException, IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpTransportMetrics.java0100644 0000000 0000000 00000002674 00000250600 026200 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; /** * Transport level metrics. * * @since 4.0 */ public interface HttpTransportMetrics { /** * Gets the number of bytes transferred. * * @return the number of bytes transferred. */ long getBytesTransferred(); } httpcore5/src/main/java/org/apache/hc/core5/http/io/ResponseOutOfOrderStrategy.java0100644 0000000 0000000 00000005716 00000250600 027307 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.ClassicHttpRequest; import java.io.IOException; import java.io.InputStream; import java.net.SocketException; import java.net.SocketTimeoutException; /** * Represents a strategy to determine how frequently the client should check for an out of order response. * An out of order response is sent before the server has read the full request. If the client fails to * check for an early response then a {@link SocketException} or {@link SocketTimeoutException} * may be thrown while writing the request entity after a timeout is reached on either the client or server. * * @since 5.1 */ @Internal public interface ResponseOutOfOrderStrategy { /** * Called before each write to the socket {@link IOException} with the number of * bytes that have already been sent, and the size of the next chunk to be written that * will occur if this check does not encounter an out of order response. * * @param request The current request. * @param connection The connection used to send the current request. * @param inputStream The response stream, this may be used to check for an early response. * @param totalBytesSent Number of bytes that have already been sent. * @param nextWriteSize The size of a socket write operation that will follow this check. * @return True if an early response was detected, otherwise false. * @throws IOException in case of a network failure while checking for an early response. */ boolean isEarlyResponseDetected( ClassicHttpRequest request, HttpClientConnection connection, InputStream inputStream, long totalBytesSent, long nextWriteSize) throws IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/io/SessionInputBuffer.java0100644 0000000 0000000 00000013543 00000250600 025607 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import java.io.IOException; import java.io.InputStream; import org.apache.hc.core5.util.CharArrayBuffer; /** * Session input buffer for HTTP/1.1 blocking connections. *

* This interface facilitates intermediate buffering of input data streamed from * an input stream and provides methods for reading lines of text. * * @since 4.0 */ public interface SessionInputBuffer { /** * Returns length data stored in the buffer * * @return data length */ int length(); /** * Returns total capacity of the buffer * * @return total capacity */ int capacity(); /** * Returns available space in the buffer. * * @return available space. */ int available(); /** * Reads up to {@code len} bytes of data from the session buffer into * an array of bytes. An attempt is made to read as many as * {@code len} bytes, but a smaller number may be read, possibly * zero. The number of bytes actually read is returned as an integer. * *

This method blocks until input data is available, end of file is * detected, or an exception is thrown. * *

If {@code off} is negative, or {@code len} is negative, or * {@code off+len} is greater than the length of the array * {@code b}, then an {@code IndexOutOfBoundsException} is * thrown. *

If {@code len} is zero, then no bytes are read and 0 is returned. * * @param b the buffer into which the data is read. * @param off the start offset in array {@code b} * at which the data is written. * @param len the maximum number of bytes to read. * @param inputStream Input stream * @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 IOException if an I/O error occurs. */ int read(byte[] b, int off, int len, InputStream inputStream) throws IOException; /** * Reads some number of bytes from the session buffer and stores them into * the buffer array {@code b}. The number of bytes actually read is * returned as an integer. This method blocks until input data is * available, end of file is detected, or an exception is thrown. * * @param b the buffer into which the data is read. * @param inputStream Input stream * @return the total number of bytes read into the buffer, or * {@code -1} is there is no more data because the end of * the stream has been reached. * @throws IOException if an I/O error occurs. */ int read(byte[] b, InputStream inputStream) throws IOException; /** * Reads the next byte of data from this session buffer. 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. * * @param inputStream Input stream * @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. */ int read(InputStream inputStream) throws IOException; /** * Reads a complete line of characters up to a line delimiter from this * session buffer into the given line buffer. The number of chars actually * read is returned as an integer. The line delimiter itself is discarded. * If no char 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, end of file is detected, or an exception is thrown. *

* The choice of a char encoding and line delimiter sequence is up to the * specific implementations of this interface. * * @param buffer the line buffer, one line of characters upon return * @param inputStream Input stream * @return the total number of bytes read into the buffer, or * {@code -1} is there is no more data because the end of * the stream has been reached. * @throws IOException if an I/O error occurs. */ int readLine(CharArrayBuffer buffer, InputStream inputStream) throws IOException; /** * Returns {@link HttpTransportMetrics} for this session buffer. * * @return transport metrics. */ HttpTransportMetrics getMetrics(); } httpcore5/src/main/java/org/apache/hc/core5/http/io/SessionOutputBuffer.java0100644 0000000 0000000 00000011000 00000250600 025772 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import java.io.IOException; import java.io.OutputStream; import org.apache.hc.core5.util.CharArrayBuffer; /** * Session output buffer for blocking HTTP/1.1 connections. *

* This interface facilitates intermediate buffering of output data streamed out * to an output stream and provides provides methods for writing lines of text. * * @since 4.0 */ public interface SessionOutputBuffer { /** * Return length data stored in the buffer * * @return data length */ int length(); /** * Returns total capacity of the buffer * * @return total capacity */ int capacity(); /** * Returns available space in the buffer. * * @return available space. */ int available(); /** * Writes {@code len} bytes from the specified byte array * starting at offset {@code off} to this session buffer. *

* If {@code off} is negative, or {@code len} is negative, or * {@code off+len} is greater than the length of the array * {@code b}, then an {@code IndexOutOfBoundsException} is thrown. * * @param b the data. * @param off the start offset in the data. * @param len the number of bytes to write. * @param outputStream the target OutputStream. * @throws IOException if an I/O error occurs. */ void write(byte[] b, int off, int len, OutputStream outputStream) throws IOException; /** * Writes {@code b.length} bytes from the specified byte array * to this session buffer. * * @param b the data. * @param outputStream the target OutputStream. * @throws IOException if an I/O error occurs. */ void write(byte[] b, OutputStream outputStream) throws IOException; /** * Writes the specified byte to this session buffer. * * @param b the {@code byte}. * @param outputStream the target OutputStream. * @throws IOException if an I/O error occurs. */ void write(int b, OutputStream outputStream) throws IOException; /** * Writes characters from the specified char array followed by a line * delimiter to this session buffer. *

* The choice of a char encoding and line delimiter sequence is up to the * specific implementations of this interface. * * @param buffer the buffer containing chars of the line. * @param outputStream the target OutputStream. * @throws IOException if an I/O error occurs. */ void writeLine(CharArrayBuffer buffer, OutputStream outputStream) throws IOException; /** * Flushes this session buffer and forces any buffered output bytes * to be written out. The general contract of {@code flush} is * that calling it is an indication that, if any bytes previously * written have been buffered by the implementation of the output * stream, such bytes should immediately be written to their * intended destination. * * @param outputStream the target OutputStream. * @throws IOException if an I/O error occurs. */ void flush(OutputStream outputStream) throws IOException; /** * Returns {@link HttpTransportMetrics} for this session buffer. * * @return transport metrics. */ HttpTransportMetrics getMetrics(); } httpcore5/src/main/java/org/apache/hc/core5/http/io/SocketConfig.java0100644 0000000 0000000 00000033420 00000250600 024364 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import java.net.SocketAddress; import java.net.SocketOptions; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; /** * Classic I/O network socket configuration. * * @since 4.3 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class SocketConfig { private static final Timeout DEFAULT_SOCKET_TIMEOUT = Timeout.ofMinutes(3); public static final SocketConfig DEFAULT = new Builder().build(); private final Timeout soTimeout; private final boolean soReuseAddress; private final TimeValue soLinger; private final boolean soKeepAlive; private final boolean tcpNoDelay; private final int sndBufSize; private final int rcvBufSize; private final int backlogSize; private final int tcpKeepIdle; private final int tcpKeepInterval; private final int tcpKeepCount; private final SocketAddress socksProxyAddress; SocketConfig( final Timeout soTimeout, final boolean soReuseAddress, final TimeValue soLinger, final boolean soKeepAlive, final boolean tcpNoDelay, final int sndBufSize, final int rcvBufSize, final int backlogSize, final int tcpKeepIdle, final int tcpKeepInterval, final int tcpKeepCount, final SocketAddress socksProxyAddress) { super(); this.soTimeout = soTimeout; this.soReuseAddress = soReuseAddress; this.soLinger = soLinger; this.soKeepAlive = soKeepAlive; this.tcpNoDelay = tcpNoDelay; this.sndBufSize = sndBufSize; this.rcvBufSize = rcvBufSize; this.backlogSize = backlogSize; this.tcpKeepIdle = tcpKeepIdle; this.tcpKeepInterval = tcpKeepInterval; this.tcpKeepCount = tcpKeepCount; this.socksProxyAddress = socksProxyAddress; } /** * @see Builder#setSoTimeout(Timeout) */ public Timeout getSoTimeout() { return soTimeout; } /** * @see Builder#setSoReuseAddress(boolean) */ public boolean isSoReuseAddress() { return soReuseAddress; } /** * @see Builder#setSoLinger(TimeValue) */ public TimeValue getSoLinger() { return soLinger; } /** * @see Builder#setSoKeepAlive(boolean) */ public boolean isSoKeepAlive() { return soKeepAlive; } /** * @see Builder#setTcpNoDelay(boolean) */ public boolean isTcpNoDelay() { return tcpNoDelay; } /** * @see Builder#setSndBufSize(int) * @since 4.4 */ public int getSndBufSize() { return sndBufSize; } /** * @see Builder#setRcvBufSize(int) * @since 4.4 */ public int getRcvBufSize() { return rcvBufSize; } /** * @see Builder#setBacklogSize(int) * @since 4.4 */ public int getBacklogSize() { return backlogSize; } /** * @see Builder#setTcpKeepIdle(int) * @since 5.3 */ public int getTcpKeepIdle() { return this.tcpKeepIdle; } /** * @see Builder#setTcpKeepInterval(int) * @since 5.3 */ public int getTcpKeepInterval() { return this.tcpKeepInterval; } /** * @see Builder#setTcpKeepCount(int) * @since 5.3 */ public int getTcpKeepCount() { return this.tcpKeepCount; } /** * @see Builder#setSocksProxyAddress(SocketAddress) */ public SocketAddress getSocksProxyAddress() { return this.socksProxyAddress; } @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("[soTimeout=").append(this.soTimeout) .append(", soReuseAddress=").append(this.soReuseAddress) .append(", soLinger=").append(this.soLinger) .append(", soKeepAlive=").append(this.soKeepAlive) .append(", tcpNoDelay=").append(this.tcpNoDelay) .append(", sndBufSize=").append(this.sndBufSize) .append(", rcvBufSize=").append(this.rcvBufSize) .append(", backlogSize=").append(this.backlogSize) .append(", tcpKeepIdle=").append(this.tcpKeepIdle) .append(", tcpKeepInterval=").append(this.tcpKeepInterval) .append(", tcpKeepCount=").append(this.tcpKeepCount) .append(", socksProxyAddress=").append(this.socksProxyAddress) .append("]"); return builder.toString(); } public static SocketConfig.Builder custom() { return new Builder(); } public static SocketConfig.Builder copy(final SocketConfig config) { Args.notNull(config, "Socket config"); return new Builder() .setSoTimeout(config.getSoTimeout()) .setSoReuseAddress(config.isSoReuseAddress()) .setSoLinger(config.getSoLinger()) .setSoKeepAlive(config.isSoKeepAlive()) .setTcpNoDelay(config.isTcpNoDelay()) .setSndBufSize(config.getSndBufSize()) .setRcvBufSize(config.getRcvBufSize()) .setBacklogSize(config.getBacklogSize()) .setTcpKeepIdle(config.getTcpKeepIdle()) .setTcpKeepInterval(config.getTcpKeepInterval()) .setTcpKeepCount(config.getTcpKeepCount()) .setSocksProxyAddress(config.getSocksProxyAddress()); } public static class Builder { private Timeout soTimeout; private boolean soReuseAddress; private TimeValue soLinger; private boolean soKeepAlive; private boolean tcpNoDelay; private int sndBufSize; private int rcvBufSize; private int backlogSize; private int tcpKeepIdle; private int tcpKeepInterval; private int tcpKeepCount; private SocketAddress socksProxyAddress; Builder() { this.soTimeout = DEFAULT_SOCKET_TIMEOUT; this.soReuseAddress = false; this.soLinger = TimeValue.NEG_ONE_SECOND; this.soKeepAlive = true; this.tcpNoDelay = true; this.sndBufSize = 0; this.rcvBufSize = 0; this.backlogSize = 0; this.tcpKeepIdle = 5; this.tcpKeepInterval = 5; this.tcpKeepCount = 3; this.socksProxyAddress = null; } /** * @see #setSoTimeout(Timeout) * * @return this instance. */ public Builder setSoTimeout(final int soTimeout, final TimeUnit timeUnit) { this.soTimeout = Timeout.of(soTimeout, timeUnit); return this; } /** * Determines the default socket timeout value for blocking I/O operations. *

* Default: 3 minutes *

* * @return this instance. * @see java.net.SocketOptions#SO_TIMEOUT */ public Builder setSoTimeout(final Timeout soTimeout) { this.soTimeout = soTimeout; return this; } /** * Determines the default value of the {@link SocketOptions#SO_REUSEADDR} parameter * for newly created sockets. *

* Default: {@code false} *

* * @return this instance. * @see java.net.SocketOptions#SO_REUSEADDR */ public Builder setSoReuseAddress(final boolean soReuseAddress) { this.soReuseAddress = soReuseAddress; return this; } /** * @see #setSoLinger(TimeValue) * * @return this instance. */ public Builder setSoLinger(final int soLinger, final TimeUnit timeUnit) { this.soLinger = Timeout.of(soLinger, timeUnit); return this; } /** * Determines the default value of the {@link SocketOptions#SO_LINGER} parameter * for newly created sockets. *

* Default: {@code -1} *

* * @return this instance. * @see java.net.SocketOptions#SO_LINGER */ public Builder setSoLinger(final TimeValue soLinger) { this.soLinger = soLinger; return this; } /** * Determines the default value of the {@link SocketOptions#SO_KEEPALIVE} parameter * for newly created sockets. *

* Default: {@code true} *

* * @return this instance. * @see java.net.SocketOptions#SO_KEEPALIVE */ public Builder setSoKeepAlive(final boolean soKeepAlive) { this.soKeepAlive = soKeepAlive; return this; } /** * Determines the default value of the {@link SocketOptions#TCP_NODELAY} parameter * for newly created sockets. *

* Default: {@code false} *

* * @return this instance. * @see java.net.SocketOptions#TCP_NODELAY */ public Builder setTcpNoDelay(final boolean tcpNoDelay) { this.tcpNoDelay = tcpNoDelay; return this; } /** * Determines the default value of the {@link SocketOptions#SO_SNDBUF} parameter * for newly created sockets. *

* Default: {@code 0} (system default) *

* * @return this instance. * @see java.net.SocketOptions#SO_SNDBUF * @since 4.4 */ public Builder setSndBufSize(final int sndBufSize) { this.sndBufSize = sndBufSize; return this; } /** * Determines the default value of the {@link SocketOptions#SO_RCVBUF} parameter * for newly created sockets. *

* Default: {@code 0} (system default) *

* * @return this instance. * @see java.net.SocketOptions#SO_RCVBUF * @since 4.4 */ public Builder setRcvBufSize(final int rcvBufSize) { this.rcvBufSize = rcvBufSize; return this; } /** * Determines the maximum queue length for incoming connection indications * (a request to connect) also known as server socket backlog. *

* Default: {@code 0} (system default) *

* * @return this instance. * @since 4.4 */ public Builder setBacklogSize(final int backlogSize) { this.backlogSize = backlogSize; return this; } /** * Determines the time (in seconds) the connection needs to remain idle before TCP starts * sending keepalive probes. *

* Default: {@code 5} *

* * @return this instance. * @since 5.3 */ public Builder setTcpKeepIdle(final int tcpKeepIdle) { this.tcpKeepIdle = tcpKeepIdle; return this; } /** * Determines the time (in seconds) between individual keepalive probes. *

* Default: {@code 5} *

* * @return this instance. * @since 5.3 */ public Builder setTcpKeepInterval(final int tcpKeepInterval) { this.tcpKeepInterval = tcpKeepInterval; return this; } /** * Determines the maximum number of keepalive probes TCP should send before dropping the connection. *

* Default: {@code 3} *

* * @return this instance. * @since 5.3 */ public Builder setTcpKeepCount(final int tcpKeepCount) { this.tcpKeepCount = tcpKeepCount; return this; } /** * The address of the SOCKS proxy to use. * * @return this instance. */ public Builder setSocksProxyAddress(final SocketAddress socksProxyAddress) { this.socksProxyAddress = socksProxyAddress; return this; } public SocketConfig build() { return new SocketConfig( Timeout.defaultsToInfinite(soTimeout), soReuseAddress, soLinger != null ? soLinger : TimeValue.NEG_ONE_SECOND, soKeepAlive, tcpNoDelay, sndBufSize, rcvBufSize, backlogSize, tcpKeepIdle, tcpKeepInterval, tcpKeepCount, socksProxyAddress); } } } httpcore5/src/main/java/org/apache/hc/core5/http/io/package-info.java0100644 0000000 0000000 00000002425 00000250600 024333 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Core HTTP transport APIs based on the classic (blocking) I/O model. */ package org.apache.hc.core5.http.io; httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/0040755 0000000 0000000 00000000000 00000250600 022460 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/AbstractHttpEntity.java0100644 0000000 0000000 00000016130 00000250600 027121 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.util.Args; /** * Abstract base class for mutable entities. Provides the commonly used attributes for streamed and * self-contained implementations. *

* This class contains immutable attributes but subclasses may contain additional immutable or mutable attributes. *

* * @since 4.0 */ public abstract class AbstractHttpEntity implements HttpEntity { static final int OUTPUT_BUFFER_SIZE = 4096; private final String contentType; private final String contentEncoding; private final boolean chunked; /** * Constructs a new instance with the given attributes kept as immutable. * * @param contentType The content-type string, may be null. * @param contentEncoding The content encoding string, may be null. * @param chunked Whether this entity should be chunked. */ protected AbstractHttpEntity(final String contentType, final String contentEncoding, final boolean chunked) { this.contentType = contentType; this.contentEncoding = contentEncoding; this.chunked = chunked; } /** * Constructs a new instance with the given attributes kept as immutable. * * @param contentType The content-type, may be null. * @param contentEncoding The content encoding string, may be null. * @param chunked Whether this entity should be chunked. */ protected AbstractHttpEntity(final ContentType contentType, final String contentEncoding, final boolean chunked) { this.contentType = Objects.toString(contentType, null); this.contentEncoding = contentEncoding; this.chunked = chunked; } /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
* * @param contentType The content-type string, may be null. * @param contentEncoding The content encoding string, may be null. */ protected AbstractHttpEntity(final String contentType, final String contentEncoding) { this(contentType, contentEncoding, false); } /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
* * @param contentType The content-type, may be null. * @param contentEncoding The content encoding string, may be null. */ protected AbstractHttpEntity(final ContentType contentType, final String contentEncoding) { this(contentType, contentEncoding, false); } /** * Writes an entity to an OutputStream. * * @param entity The entity to write, never null. * @param outStream Where to write the entity, never null. * @throws IOException if the entity cannot generate its content stream; also thrown if the output stream is closed. * @throws UnsupportedOperationException if entity content cannot be represented as {@link InputStream}. */ public static void writeTo(final HttpEntity entity, final OutputStream outStream) throws IOException { Args.notNull(entity, "Entity"); Args.notNull(outStream, "Output stream"); try (final InputStream inStream = entity.getContent()) { if (inStream != null) { int count; final byte[] tmp = new byte[OUTPUT_BUFFER_SIZE]; while ((count = inStream.read(tmp)) != -1) { outStream.write(tmp, 0, count); } } } } /** * Writes this entity to an OutputStream. * * @param outStream Where to write the entity, never null. * @throws IOException if the entity cannot generate its content stream; also thrown if the output stream is closed. * @throws UnsupportedOperationException if entity content cannot be represented as {@link InputStream}. */ @Override public void writeTo(final OutputStream outStream) throws IOException { writeTo(this, outStream); } @Override public final String getContentType() { return contentType; } @Override public final String getContentEncoding() { return contentEncoding; } /** * {@inheritDoc} *

* This implementation always returns {@code false}. *

*/ @Override public final boolean isChunked() { return chunked; } /** * {@inheritDoc} *

* This implementation always returns {@code false}. *

*/ @Override public boolean isRepeatable() { return false; } /** * {@inheritDoc} *

* This implementation always returns {@code null}. *

*/ @Override public Supplier> getTrailers() { return null; } /** * {@inheritDoc} *

* This implementation always returns an immutable empty set. *

*/ @Override public Set getTrailerNames() { return Collections.emptySet(); } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("[Entity-Class: "); sb.append(getClass().getSimpleName()); sb.append(", Content-Type: "); sb.append(contentType); sb.append(", Content-Encoding: "); sb.append(contentEncoding); sb.append(", chunked: "); sb.append(chunked); sb.append(']'); return sb.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/BasicHttpEntity.java0100644 0000000 0000000 00000015457 00000250600 026412 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.IOException; import java.io.InputStream; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.io.Closer; import org.apache.hc.core5.util.Args; /** * A generic streamed, non-repeatable entity that obtains its content from an {@link InputStream}. *

* This class contains {@link ThreadingBehavior#IMMUTABLE_CONDITIONAL immutable attributes} but subclasses may contain * additional immutable or mutable attributes. *

* * @see ThreadingBehavior#IMMUTABLE_CONDITIONAL * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class BasicHttpEntity extends AbstractHttpEntity { private final InputStream content; private final long length; /** * Constructs a new instance with the given attributes kept as immutable. * * @param content The message body contents as an InputStream. * @param contentLength The value for the {@code Content-Length} header for the size of the message body, in bytes. * @param contentType The content-type, may be null. * @param contentEncoding The content encoding string, may be null. * @param chunked Whether this entity should be chunked. */ public BasicHttpEntity( final InputStream content, final long contentLength, final ContentType contentType, final String contentEncoding, final boolean chunked) { super(contentType, contentEncoding, chunked); this.content = Args.notNull(content, "Content stream"); this.length = contentLength; } /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
* * @param content The message body contents as an InputStream. * @param contentLength The value for the {@code Content-Length} header for the size of the message body, in bytes. * @param contentType The content-type, may be null. * @param contentEncoding The content encoding string, may be null. */ public BasicHttpEntity( final InputStream content, final long contentLength, final ContentType contentType, final String contentEncoding) { this(content, contentLength, contentType, contentEncoding, false); } /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
  • does not define a content encoding.
  • *
* * @param content The message body contents as an InputStream. * @param contentLength The value for the {@code Content-Length} header for the size of the message body, in bytes. * @param contentType The content-type, may be null. */ public BasicHttpEntity(final InputStream content, final long contentLength, final ContentType contentType) { this(content, contentLength, contentType, null); } /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
  • does not define a length.
  • *
* * @param content The message body contents as an InputStream. * @param contentType The content-type, may be null. * @param contentEncoding The content encoding string, may be null. */ public BasicHttpEntity(final InputStream content, final ContentType contentType, final String contentEncoding) { this(content, -1, contentType, contentEncoding); } /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
  • does not define a length.
  • *
  • does not define a content encoding.
  • *
* * @param content The message body contents as an InputStream. * @param contentType The content-type, may be null. */ public BasicHttpEntity(final InputStream content, final ContentType contentType) { this(content, -1, contentType, null); } /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • does not define a length.
  • *
  • does not define a content encoding.
  • *
* * @param content The message body contents as an InputStream. * @param contentType The content-type, may be null. * @param chunked Whether this entity should be chunked. */ public BasicHttpEntity(final InputStream content, final ContentType contentType, final boolean chunked) { this(content, -1, contentType, null, chunked); } @Override public final long getContentLength() { return this.length; } @Override public final InputStream getContent() throws IllegalStateException { return this.content; } /** * {@inheritDoc} *

* This implementation always returns {@code false}. *

*/ @Override public final boolean isRepeatable() { return false; } /** * {@inheritDoc} *

* This implementation always returns {@code true}. *

*/ @Override public final boolean isStreaming() { return true; } @Override public final void close() throws IOException { Closer.close(content); } } httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/BufferedHttpEntity.java0100644 0000000 0000000 00000007350 00000250600 027104 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.util.Args; /** * A wrapping entity that buffers it content if necessary. * The buffered entity is always repeatable. * If the wrapped entity is repeatable itself, calls are passed through. * If the wrapped entity is not repeatable, the content is read into a * buffer once and provided from there as often as required. * * @since 4.0 */ public class BufferedHttpEntity extends HttpEntityWrapper { private final byte[] buffer; /** * Creates a new buffered entity wrapper. * * @param entity the entity to wrap, not null * @throws IllegalArgumentException if wrapped is null * @throws IOException in case of an I/O error. */ public BufferedHttpEntity(final HttpEntity entity) throws IOException { super(entity); if (!entity.isRepeatable() || entity.getContentLength() < 0) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); entity.writeTo(out); out.flush(); this.buffer = out.toByteArray(); } else { this.buffer = null; } } @Override public long getContentLength() { if (this.buffer != null) { return this.buffer.length; } return super.getContentLength(); } @Override public InputStream getContent() throws IOException { if (this.buffer != null) { return new ByteArrayInputStream(this.buffer); } return super.getContent(); } /** * Tells that this entity does not have to be chunked. * * @return {@code false} */ @Override public boolean isChunked() { return buffer == null && super.isChunked(); } /** * Tells that this entity is repeatable. * * @return {@code true} */ @Override public boolean isRepeatable() { return true; } @Override public void writeTo(final OutputStream outStream) throws IOException { Args.notNull(outStream, "Output stream"); if (this.buffer != null) { outStream.write(this.buffer); } else { super.writeTo(outStream); } } // non-javadoc, see interface HttpEntity @Override public boolean isStreaming() { return buffer == null && super.isStreaming(); } } // class BufferedHttpEntity httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/ByteArrayEntity.java0100644 0000000 0000000 00000023067 00000250600 026427 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.util.Args; /** * A self contained, repeatable entity that obtains its content from a byte array. *

* This class contains {@link ThreadingBehavior#IMMUTABLE_CONDITIONAL immutable attributes} but subclasses may contain * additional immutable or mutable attributes. *

* * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class ByteArrayEntity extends AbstractHttpEntity { private final byte[] buf; private final int off, len; /** * Constructs a new instance with the given attributes kept as immutable. * * @param buf The message body contents as a byte array buffer. * @param off The offset in the buffer of the first byte to read. * @param len The maximum number of bytes to read from the buffer. * @param contentType The content-type, may be null. * @param contentEncoding The content encoding string, may be null. * @param chunked Whether this entity should be chunked. * @since 5.0 */ public ByteArrayEntity( final byte[] buf, final int off, final int len, final ContentType contentType, final String contentEncoding, final boolean chunked) { super(contentType, contentEncoding, chunked); Args.notNull(buf, "Source byte array"); Args.notNegative(off, "offset"); Args.notNegative(len, "length"); Args.notNegative(off + len, "off + len"); Args.check(off <= buf.length, "off %s cannot be greater then b.length %s ", off, buf.length); Args.check(off + len <= buf.length, "off + len %s cannot be less then b.length %s ", off + len, buf.length); this.buf = buf; this.off = off; this.len = len; } /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
* * @param buf The message body contents as a byte array buffer. * @param off The offset in the buffer of the first byte to read. * @param len The maximum number of bytes to read from the buffer. * @param contentType The content-type, may be null. * @param contentEncoding The content encoding string, may be null. * @since 5.0 */ public ByteArrayEntity( final byte[] buf, final int off, final int len, final ContentType contentType, final String contentEncoding) { this(buf, off, len, contentType, contentEncoding, false); } /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • starts reading it's contents at index 0 in the buffer.
  • *
  • has the content length of the given buffer.
  • *
* * @param buf The message body contents as a byte array buffer. * @param contentType The content-type, may be null. * @param contentEncoding The content encoding string, may be null. * @param chunked Whether this entity should be chunked. * @since 5.0 */ public ByteArrayEntity( final byte[] buf, final ContentType contentType, final String contentEncoding, final boolean chunked) { super(contentType, contentEncoding, chunked); Args.notNull(buf, "Source byte array"); this.buf = buf; this.off = 0; this.len = this.buf.length; } /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
  • starts reading it's contents at index 0 in the buffer.
  • *
  • has the content length of the given buffer.
  • *
* * @param buf The message body contents as a byte array buffer. * @param contentType The content-type, may be null. * @param contentEncoding The content encoding string, may be null. * @since 5.0 */ public ByteArrayEntity(final byte[] buf, final ContentType contentType, final String contentEncoding) { this(buf, contentType, contentEncoding, false); } /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • starts reading it's contents at index 0 in the buffer.
  • *
  • has the content length of the given buffer.
  • *
  • does not define a content encoding.
  • *
* * @param buf The message body contents as a byte array buffer. * @param contentType The content-type, may be null. * @param chunked Whether this entity should be chunked. */ public ByteArrayEntity(final byte[] buf, final ContentType contentType, final boolean chunked) { this(buf, contentType, null, chunked); } /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
  • starts reading it's contents at index 0 in the buffer.
  • *
  • has the content length of the given buffer.
  • *
  • does not define a content encoding.
  • *
* * @param buf The message body contents as a byte array buffer. * @param contentType The content-type, may be null. */ public ByteArrayEntity(final byte[] buf, final ContentType contentType) { this(buf, contentType, null, false); } /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • does not define a content encoding.
  • *
* * @param buf The message body contents as a byte array buffer. * @param off The offset in the buffer of the first byte to read. * @param len The maximum number of bytes to read from the buffer. * @param contentType The content-type, may be null. * @param chunked Whether this entity should be chunked. */ public ByteArrayEntity( final byte[] buf, final int off, final int len, final ContentType contentType, final boolean chunked) { this(buf, off, len, contentType, null, chunked); } /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
  • does not define a content encoding.
  • *
* * @param buf The message body contents as a byte array buffer. * @param off The offset in the buffer of the first byte to read. * @param len The maximum number of bytes to read from the buffer. * @param contentType The content-type, may be null. * @since 5.0 */ public ByteArrayEntity(final byte[] buf, final int off, final int len, final ContentType contentType) { this(buf, off, len, contentType, null, false); } /** * {@inheritDoc} *

* This implementation always returns {@code true}. *

*/ @Override public final boolean isRepeatable() { return true; } @Override public final long getContentLength() { return this.len; } @Override public final InputStream getContent() { return new ByteArrayInputStream(this.buf, this.off, this.len); } @Override public final void writeTo(final OutputStream outStream) throws IOException { Args.notNull(outStream, "Output stream"); outStream.write(this.buf, this.off, this.len); outStream.flush(); } /** * {@inheritDoc} *

* This implementation always returns {@code false}. *

*/ @Override public final boolean isStreaming() { return false; } /** * {@inheritDoc} *

* This implementation is a no-op. *

*/ @Override public final void close() throws IOException { } } httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/ByteBufferEntity.java0100644 0000000 0000000 00000010364 00000250600 026556 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.util.Args; /** * An entity that delivers the contents of a {@link ByteBuffer}. */ public class ByteBufferEntity extends AbstractHttpEntity { private final ByteBuffer buffer; private final long length; /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
* * @param buffer The message body contents as a byte buffer. * @param contentType The content-type, may be null. * @param contentEncoding The content encoding string, may be null. */ public ByteBufferEntity(final ByteBuffer buffer, final ContentType contentType, final String contentEncoding) { super(contentType, contentEncoding); Args.notNull(buffer, "Source byte buffer"); this.buffer = buffer; this.length = buffer.remaining(); } /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
  • does not define a content encoding.
  • *
* * @param buffer The message body contents as a byte buffer. * @param contentType The content-type, may be null. */ public ByteBufferEntity(final ByteBuffer buffer, final ContentType contentType) { this(buffer, contentType, null); } /** * {@inheritDoc} *

* This implementation always returns {@code true}. *

*/ @Override public final boolean isRepeatable() { return false; } @Override public final long getContentLength() { return length; } @Override public final InputStream getContent() throws IOException, UnsupportedOperationException { return new InputStream() { @Override public int read() throws IOException { if (!buffer.hasRemaining()) { return -1; } return buffer.get() & 0xFF; } @Override public int read(final byte[] bytes, final int off, final int len) throws IOException { if (len == 0) { return 0; } if (!buffer.hasRemaining()) { return -1; } final int chunk = Math.min(len, buffer.remaining()); buffer.get(bytes, off, chunk); return chunk; } }; } /** * {@inheritDoc} *

* This implementation always returns {@code false}. *

*/ @Override public final boolean isStreaming() { return false; } /** * {@inheritDoc} *

* This implementation is a no-op. *

*/ @Override public final void close() throws IOException { } } httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/EmptyInputStream.java0100644 0000000 0000000 00000005221 00000250600 026612 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.InputStream; /** * @since 5.1 */ public final class EmptyInputStream extends InputStream { /** * Singleton instance of EmptyInputStream. */ public static final EmptyInputStream INSTANCE = new EmptyInputStream(); private EmptyInputStream() { // noop. } /** * Returns {@code 0}. */ @Override public int available() { return 0; } /** * Noop. */ @Override public void close() { // noop. } /** * Noop. */ @SuppressWarnings("sync-override") @Override public void mark(final int readLimit) { // noop. } /** * Returns {@code true}. */ @Override public boolean markSupported() { return true; } /** * Returns {@code -1}. */ @Override public int read() { return -1; } /** * Returns {@code -1}. */ @Override public int read(final byte[] buf) { return -1; } /** * Returns {@code -1}. */ @Override public int read(final byte[] buf, final int off, final int len) { if (len == 0) { return 0; } return -1; } /** * Noop. */ @SuppressWarnings("sync-override") @Override public void reset() { // noop } /** * Returns {@code 0}. */ @Override public long skip(final long n) { return 0L; } } httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/EntityTemplate.java0100644 0000000 0000000 00000010744 00000250600 026276 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.io.IOCallback; import org.apache.hc.core5.util.Args; /** * Entity that delegates the process of content generation to a {@link IOCallback} * with {@link OutputStream} as output sink. *

* This class contains {@link ThreadingBehavior#IMMUTABLE_CONDITIONAL immutable attributes} but subclasses may contain * additional immutable or mutable attributes. *

* * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public final class EntityTemplate extends AbstractHttpEntity { private final long contentLength; private final IOCallback callback; /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
* * @param contentLength The value for the {@code Content-Length} header for the size of the message body, in bytes. * @param contentType The content-type, may be null. * @param contentEncoding The content encoding string, may be null. * @param callback A consumer that write the message body to an output stream. */ public EntityTemplate( final long contentLength, final ContentType contentType, final String contentEncoding, final IOCallback callback) { super(contentType, contentEncoding); this.contentLength = contentLength; this.callback = Args.notNull(callback, "I/O callback"); } /** * @since 5.4 */ public EntityTemplate( final long contentLength, final ContentType contentType, final IOCallback callback) { this(contentLength, contentType, null, callback); } /** * @since 5.4 */ public EntityTemplate(final ContentType contentType, final IOCallback callback) { this(-1, contentType, null, callback); } @Override public long getContentLength() { return contentLength; } @Override public InputStream getContent() throws IOException { final ByteArrayOutputStream buf = new ByteArrayOutputStream(); writeTo(buf); return new ByteArrayInputStream(buf.toByteArray()); } /** * {@inheritDoc} *

* This implementation always returns {@code true}. *

*/ @Override public boolean isRepeatable() { return true; } @Override public void writeTo(final OutputStream outStream) throws IOException { Args.notNull(outStream, "Output stream"); this.callback.execute(outStream); } /** * {@inheritDoc} *

* This implementation always returns {@code false}. *

*/ @Override public boolean isStreaming() { return false; } /** * {@inheritDoc} *

* This implementation is a no-op. *

*/ @Override public void close() throws IOException { } } httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/EntityUtils.java0100644 0000000 0000000 00000046060 00000250600 025623 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.charset.UnsupportedCharsetException; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.io.Closer; import org.apache.hc.core5.net.WWWFormCodec; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.ByteArrayBuffer; import org.apache.hc.core5.util.CharArrayBuffer; /** * Support methods for {@link HttpEntity}. * * @since 4.0 */ public final class EntityUtils { // TODO Consider using a sane value, but what is sane? 1 GB? 100 MB? 10 MB? private static final int DEFAULT_ENTITY_RETURN_MAX_LENGTH = Integer.MAX_VALUE; private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; private static final int DEFAULT_CHAR_BUFFER_SIZE = 1024; private static final int DEFAULT_BYTE_BUFFER_SIZE = 4096; private EntityUtils() { // NoOp } /** * Ensures that the entity content is fully consumed and the content stream, if exists, * is closed. The process is done, quietly , without throwing any IOException. * * @param entity the entity to consume. * * @since 4.2 */ public static void consumeQuietly(final HttpEntity entity) { try { consume(entity); } catch (final IOException ignore) { // Ignore exception } } /** * Ensures that the entity content is fully consumed and the content stream, if exists, * is closed. * * @param entity the entity to consume. * @throws IOException if an error occurs reading the input stream * * @since 4.1 */ public static void consume(final HttpEntity entity) throws IOException { if (entity == null) { return; } if (entity.isStreaming()) { Closer.close(entity.getContent()); } } /** * Gets a usable content length value for the given candidate. * * @param contentLength an integer. * @return The given content length or {@value #DEFAULT_BYTE_BUFFER_SIZE} if it is < 0. */ private static int toContentLength(final int contentLength) { return contentLength < 0 ? DEFAULT_BYTE_BUFFER_SIZE : contentLength; } static long checkContentLength(final EntityDetails entityDetails) { // -1 is a special value, // 0 is allowed as well, // but never more than Integer.MAX_VALUE. return Args.checkRange(entityDetails.getContentLength(), -1, Integer.MAX_VALUE, "HTTP entity too large to be buffered in memory)"); } /** * Reads the contents of an entity and return it as a byte array. * * @param entity the entity to read from= * @return byte array containing the entity content. May be null if * {@link HttpEntity#getContent()} is null. * @throws IOException if an error occurs reading the input stream * @throws IllegalArgumentException if entity is null or if content length > Integer.MAX_VALUE */ public static byte[] toByteArray(final HttpEntity entity) throws IOException { Args.notNull(entity, "HttpEntity"); final int contentLength = toContentLength((int) checkContentLength(entity)); try (final InputStream inStream = entity.getContent()) { if (inStream == null) { return null; } final ByteArrayBuffer buffer = new ByteArrayBuffer(contentLength); final byte[] tmp = new byte[DEFAULT_BYTE_BUFFER_SIZE]; int l; while ((l = inStream.read(tmp)) != -1) { buffer.append(tmp, 0, l); } return buffer.toByteArray(); } } /** * Reads the contents of an entity and return it as a byte array. * * @param entity the entity to read from= * @return byte array containing the entity content. May be null if * {@link HttpEntity#getContent()} is null. * @param maxResultLength * The maximum size of the String to return; use it to guard against unreasonable or malicious processing. * @throws IOException if an error occurs reading the input stream * @throws IllegalArgumentException if entity is null or if content length > Integer.MAX_VALUE */ public static byte[] toByteArray(final HttpEntity entity, final int maxResultLength) throws IOException { Args.notNull(entity, "HttpEntity"); final int contentLength = toContentLength((int) checkContentLength(entity)); try (final InputStream inStream = entity.getContent()) { if (inStream == null) { return null; } final ByteArrayBuffer buffer = new ByteArrayBuffer(Math.min(maxResultLength, contentLength)); final byte[] tmp = new byte[DEFAULT_BYTE_BUFFER_SIZE]; int l; while ((l = inStream.read(tmp)) != -1 && buffer.length() < maxResultLength) { buffer.append(tmp, 0, l); } buffer.setLength(Math.min(buffer.length(), maxResultLength)); return buffer.toByteArray(); } } private static CharArrayBuffer toCharArrayBuffer(final InputStream inStream, final int contentLength, final Charset charset, final int maxResultLength) throws IOException { Args.notNull(inStream, "InputStream"); Args.positive(maxResultLength, "maxResultLength"); final Charset actualCharset = charset == null ? DEFAULT_CHARSET : charset; final CharArrayBuffer buf = new CharArrayBuffer( Math.min(maxResultLength, contentLength > 0 ? contentLength : DEFAULT_CHAR_BUFFER_SIZE)); final Reader reader = new InputStreamReader(inStream, actualCharset); final char[] tmp = new char[DEFAULT_CHAR_BUFFER_SIZE]; int chReadCount; while ((chReadCount = reader.read(tmp)) != -1 && buf.length() < maxResultLength) { buf.append(tmp, 0, chReadCount); } buf.setLength(Math.min(buf.length(), maxResultLength)); return buf; } private static final Map CONTENT_TYPE_MAP; static { final ContentType[] contentTypes = { ContentType.APPLICATION_ATOM_XML, ContentType.APPLICATION_FORM_URLENCODED, ContentType.APPLICATION_JSON, ContentType.APPLICATION_SVG_XML, ContentType.APPLICATION_XHTML_XML, ContentType.APPLICATION_XML, ContentType.MULTIPART_FORM_DATA, ContentType.TEXT_HTML, ContentType.TEXT_PLAIN, ContentType.TEXT_XML }; final HashMap map = new HashMap<>(); for (final ContentType contentType: contentTypes) { map.put(contentType.getMimeType(), contentType); } CONTENT_TYPE_MAP = Collections.unmodifiableMap(map); } private static String toString(final HttpEntity entity, final ContentType contentType, final int maxResultLength) throws IOException { Args.notNull(entity, "HttpEntity"); final int contentLength = toContentLength((int) checkContentLength(entity)); try (final InputStream inStream = entity.getContent()) { if (inStream == null) { return null; } Charset charset = null; if (contentType != null) { charset = contentType.getCharset(); if (charset == null) { final ContentType defaultContentType = CONTENT_TYPE_MAP.get(contentType.getMimeType()); charset = defaultContentType != null ? defaultContentType.getCharset() : null; } } return toCharArrayBuffer(inStream, contentLength, charset, maxResultLength).toString(); } } /** * Gets the entity content as a String, using the provided default character set * if none is found in the entity. * If defaultCharset is null, the default "UTF-8" is used. * * @param entity must not be null * @param defaultCharset character set to be applied if none found in the entity, * or if the entity provided charset is invalid or not available. * @return the entity content as a String. May be null if * {@link HttpEntity#getContent()} is null. * @throws ParseException if header elements cannot be parsed * @throws IllegalArgumentException if entity is null or if content length > Integer.MAX_VALUE * @throws IOException if an error occurs reading the input stream * @throws java.nio.charset.UnsupportedCharsetException Thrown when the named entity's charset is not available in * this instance of the Java virtual machine and no defaultCharset is provided. */ public static String toString( final HttpEntity entity, final Charset defaultCharset) throws IOException, ParseException { return toString(entity, defaultCharset, DEFAULT_ENTITY_RETURN_MAX_LENGTH); } /** * Gets the entity content as a String, using the provided default character set * if none is found in the entity. * If defaultCharset is null, the default "UTF-8" is used. * * @param entity must not be null * @param defaultCharset character set to be applied if none found in the entity, * or if the entity provided charset is invalid or not available. * @param maxResultLength * The maximum size of the String to return; use it to guard against unreasonable or malicious processing. * @return the entity content as a String. May be null if * {@link HttpEntity#getContent()} is null. * @throws ParseException if header elements cannot be parsed * @throws IllegalArgumentException if entity is null or if content length > Integer.MAX_VALUE * @throws IOException if an error occurs reading the input stream * @throws java.nio.charset.UnsupportedCharsetException Thrown when the named entity's charset is not available in * this instance of the Java virtual machine and no defaultCharset is provided. */ public static String toString( final HttpEntity entity, final Charset defaultCharset, final int maxResultLength) throws IOException, ParseException { Args.notNull(entity, "HttpEntity"); ContentType contentType = null; try { contentType = ContentType.parse(entity.getContentType()); } catch (final UnsupportedCharsetException ex) { if (defaultCharset == null) { throw new UnsupportedEncodingException(ex.getMessage()); } } if (contentType != null) { if (contentType.getCharset() == null) { contentType = contentType.withCharset(defaultCharset); } } else { contentType = ContentType.DEFAULT_TEXT.withCharset(defaultCharset); } return toString(entity, contentType, maxResultLength); } /** * Gets the entity content as a String, using the provided default character set * if none is found in the entity. * If defaultCharset is null, the default "UTF-8" is used. * * @param entity must not be null * @param defaultCharset character set to be applied if none found in the entity * @return the entity content as a String. May be null if * {@link HttpEntity#getContent()} is null. * @throws ParseException if header elements cannot be parsed * @throws IllegalArgumentException if entity is null or if content length > Integer.MAX_VALUE * @throws IOException if an error occurs reading the input stream * @throws java.nio.charset.UnsupportedCharsetException Thrown when the named charset is not available in * this instance of the Java virtual machine */ public static String toString( final HttpEntity entity, final String defaultCharset) throws IOException, ParseException { return toString(entity, defaultCharset, DEFAULT_ENTITY_RETURN_MAX_LENGTH); } /** * Gets the entity content as a String, using the provided default character set * if none is found in the entity. * If defaultCharset is null, the default "UTF-8" is used. * * @param entity must not be null * @param defaultCharset character set to be applied if none found in the entity * @param maxResultLength * The maximum size of the String to return; use it to guard against unreasonable or malicious processing. * @return the entity content as a String. May be null if * {@link HttpEntity#getContent()} is null. * @throws ParseException if header elements cannot be parsed * @throws IllegalArgumentException if entity is null or if content length > Integer.MAX_VALUE * @throws IOException if an error occurs reading the input stream * @throws java.nio.charset.UnsupportedCharsetException Thrown when the named charset is not available in * this instance of the Java virtual machine */ public static String toString( final HttpEntity entity, final String defaultCharset, final int maxResultLength) throws IOException, ParseException { return toString(entity, defaultCharset != null ? Charset.forName(defaultCharset) : null, maxResultLength); } /** * Reads the contents of an entity and return it as a String. * The content is converted using the character set from the entity (if any), * failing that, "UTF-8" is used. * * @param entity the entity to convert to a string; must not be null * @return String containing the content. * @throws ParseException if header elements cannot be parsed * @throws IllegalArgumentException if entity is null or if content length > Integer.MAX_VALUE * @throws IOException if an error occurs reading the input stream * @throws java.nio.charset.UnsupportedCharsetException Thrown when the named charset is not available in * this instance of the Java virtual machine */ public static String toString(final HttpEntity entity) throws IOException, ParseException { return toString(entity, DEFAULT_ENTITY_RETURN_MAX_LENGTH); } /** * Reads the contents of an entity and return it as a String. * The content is converted using the character set from the entity (if any), * failing that, "UTF-8" is used. * * @param entity the entity to convert to a string; must not be null * @param maxResultLength * The maximum size of the String to return; use it to guard against unreasonable or malicious processing. * @return String containing the content. * @throws ParseException if header elements cannot be parsed * @throws IllegalArgumentException if entity is null or if content length > Integer.MAX_VALUE * @throws IOException if an error occurs reading the input stream * @throws java.nio.charset.UnsupportedCharsetException Thrown when the named charset is not available in * this instance of the Java virtual machine */ public static String toString(final HttpEntity entity, final int maxResultLength) throws IOException, ParseException { Args.notNull(entity, "HttpEntity"); return toString(entity, ContentType.parse(entity.getContentType()), maxResultLength); } /** * Returns a list of {@link NameValuePair NameValuePairs} as parsed from an {@link HttpEntity}. * The encoding is taken from the entity's Content-Encoding header. *

* This is typically used while parsing an HTTP POST. *

* * @param entity * The entity to parse * @return a list of {@link NameValuePair} as built from the URI's query portion. * @throws IOException * If there was an exception getting the entity's data. */ public static List parse(final HttpEntity entity) throws IOException { return parse(entity, DEFAULT_ENTITY_RETURN_MAX_LENGTH); } /** * Returns a list of {@link NameValuePair NameValuePairs} as parsed from an {@link HttpEntity}. * The encoding is taken from the entity's Content-Encoding header. *

* This is typically used while parsing an HTTP POST. *

* * @param entity * The entity to parse * @param maxStreamLength * The maximum size of the stream to read; use it to guard against unreasonable or malicious processing. * @return a list of {@link NameValuePair} as built from the URI's query portion. * @throws IOException * If there was an exception getting the entity's data. */ public static List parse(final HttpEntity entity, final int maxStreamLength) throws IOException { Args.notNull(entity, "HttpEntity"); final int contentLength = toContentLength((int) checkContentLength(entity)); final ContentType contentType = ContentType.parse(entity.getContentType()); if (!ContentType.APPLICATION_FORM_URLENCODED.isSameMimeType(contentType)) { return Collections.emptyList(); } final Charset charset = contentType.getCharset(DEFAULT_CHARSET); final CharArrayBuffer buf; try (final InputStream inStream = entity.getContent()) { if (inStream == null) { return Collections.emptyList(); } buf = toCharArrayBuffer(inStream, contentLength, charset, maxStreamLength); } if (buf.isEmpty()) { return Collections.emptyList(); } return WWWFormCodec.parse(buf, charset); } } httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/FileEntity.java0100644 0000000 0000000 00000010015 00000250600 025371 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.util.Args; /** * A self contained, repeatable entity that obtains its content from a file. *

* This class contains {@link ThreadingBehavior#IMMUTABLE_CONDITIONAL immutable attributes} but subclasses may contain * additional immutable or mutable attributes. *

* @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class FileEntity extends AbstractHttpEntity { private final File file; /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
* * @param file The message body contents will be set from this file. * @param contentType The content-type, may be null. * @param contentEncoding The content encoding string, may be null. */ public FileEntity(final File file, final ContentType contentType, final String contentEncoding) { super(contentType, contentEncoding); this.file = Args.notNull(file, "File"); } /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
  • does not define a content encoding.
  • *
* * @param file The message body contents will be set from this file. * @param contentType The content-type, may be null. */ public FileEntity(final File file, final ContentType contentType) { super(contentType, null); this.file = Args.notNull(file, "File"); } /** * {@inheritDoc} *

* This implementation always returns {@code true}. *

*/ @Override public final boolean isRepeatable() { return true; } /** * {@inheritDoc} *

* This implementation returns the underlying file's current file length. *

*/ @Override public final long getContentLength() { return this.file.length(); } @Override public final InputStream getContent() throws IOException { return new FileInputStream(this.file); } /** * {@inheritDoc} *

* This implementation always returns {@code false}. *

*/ @Override public final boolean isStreaming() { return false; } /** * {@inheritDoc} *

* This implementation is a no-op. *

*/ @Override public final void close() throws IOException { // do nothing } } httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/HttpEntities.java0100644 0000000 0000000 00000020261 00000250600 025745 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.nio.charset.Charset; import java.nio.file.Path; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.zip.GZIPOutputStream; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.io.IOCallback; import org.apache.hc.core5.net.WWWFormCodec; import org.apache.hc.core5.util.Args; /** * {@link HttpEntity} factory methods. * * @since 5.0 */ public final class HttpEntities { private HttpEntities() { } public static HttpEntity create(final String content, final ContentType contentType) { return new StringEntity(content, contentType); } public static HttpEntity create(final String content, final Charset charset) { return new StringEntity(content, ContentType.TEXT_PLAIN.withCharset(charset)); } public static HttpEntity create(final String content) { return new StringEntity(content, ContentType.TEXT_PLAIN); } public static HttpEntity create(final byte[] content, final ContentType contentType) { return new ByteArrayEntity(content, contentType); } public static HttpEntity create(final File content, final ContentType contentType) { return new FileEntity(content, contentType); } public static HttpEntity create(final Serializable serializable, final ContentType contentType) { return new SerializableEntity(serializable, contentType); } public static HttpEntity createUrlEncoded( final Iterable parameters, final Charset charset) { final ContentType contentType = charset != null ? ContentType.APPLICATION_FORM_URLENCODED.withCharset(charset) : ContentType.APPLICATION_FORM_URLENCODED; return create(WWWFormCodec.format(parameters, contentType.getCharset()), contentType); } public static HttpEntity create(final IOCallback callback, final ContentType contentType) { return new EntityTemplate(-1, contentType, null, callback); } public static HttpEntity gzip(final HttpEntity entity) { return new HttpEntityWrapper(entity) { @Override public String getContentEncoding() { return "gzip"; } @Override public long getContentLength() { return -1; } @Override public InputStream getContent() throws IOException { throw new UnsupportedOperationException(); } @Override public void writeTo(final OutputStream outStream) throws IOException { Args.notNull(outStream, "Output stream"); final GZIPOutputStream gzip = new GZIPOutputStream(outStream); super.writeTo(gzip); // Only close output stream if the wrapped entity has been // successfully written out gzip.close(); } }; } public static HttpEntity createGzipped(final String content, final ContentType contentType) { return gzip(create(content, contentType)); } public static HttpEntity createGzipped(final String content, final Charset charset) { return gzip(create(content, charset)); } public static HttpEntity createGzipped(final String content) { return gzip(create(content)); } public static HttpEntity createGzipped(final byte[] content, final ContentType contentType) { return gzip(create(content, contentType)); } public static HttpEntity createGzipped(final File content, final ContentType contentType) { return gzip(create(content, contentType)); } public static HttpEntity createGzipped(final Serializable serializable, final ContentType contentType) { return gzip(create(serializable, contentType)); } public static HttpEntity createGzipped(final IOCallback callback, final ContentType contentType) { return gzip(create(callback, contentType)); } public static HttpEntity createGzipped(final Path content, final ContentType contentType) { return gzip(create(content, contentType)); } public static HttpEntity withTrailers(final HttpEntity entity, final Header... trailers) { return new HttpEntityWrapper(entity) { @Override public boolean isChunked() { // Must be chunk coded return true; } @Override public long getContentLength() { return -1; } @Override public Supplier> getTrailers() { return () -> Arrays.asList(trailers); } @Override public Set getTrailerNames() { final Set names = new LinkedHashSet<>(); for (final Header trailer: trailers) { names.add(trailer.getName()); } return names; } }; } public static HttpEntity create(final String content, final ContentType contentType, final Header... trailers) { return withTrailers(create(content, contentType), trailers); } public static HttpEntity create(final String content, final Charset charset, final Header... trailers) { return withTrailers(create(content, charset), trailers); } public static HttpEntity create(final String content, final Header... trailers) { return withTrailers(create(content), trailers); } public static HttpEntity create(final byte[] content, final ContentType contentType, final Header... trailers) { return withTrailers(create(content, contentType), trailers); } public static HttpEntity create(final File content, final ContentType contentType, final Header... trailers) { return withTrailers(create(content, contentType), trailers); } public static HttpEntity create( final Serializable serializable, final ContentType contentType, final Header... trailers) { return withTrailers(create(serializable, contentType), trailers); } public static HttpEntity create( final IOCallback callback, final ContentType contentType, final Header... trailers) { return withTrailers(create(callback, contentType), trailers); } public static HttpEntity create(final Path content, final ContentType contentType) { return new PathEntity(content, contentType); } public static HttpEntity create(final Path content, final ContentType contentType, final Header... trailers) { return withTrailers(create(content, contentType), trailers); } } httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/HttpEntityWrapper.java0100644 0000000 0000000 00000007137 00000250600 027005 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; import java.util.Set; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.util.Args; /** * Base class for wrapping entities that delegates all calls to the wrapped entity. * Implementations can derive from this class and override only those methods that * should not be delegated to the wrapped entity. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class HttpEntityWrapper implements HttpEntity { /** The wrapped entity. */ private final HttpEntity wrappedEntity; /** * Creates a new entity wrapper. * * @param wrappedEntity the entity to wrap. */ public HttpEntityWrapper(final HttpEntity wrappedEntity) { super(); this.wrappedEntity = Args.notNull(wrappedEntity, "Wrapped entity"); } @Override public boolean isRepeatable() { return wrappedEntity.isRepeatable(); } @Override public boolean isChunked() { return wrappedEntity.isChunked(); } @Override public long getContentLength() { return wrappedEntity.getContentLength(); } @Override public String getContentType() { return wrappedEntity.getContentType(); } @Override public String getContentEncoding() { return wrappedEntity.getContentEncoding(); } @Override public InputStream getContent() throws IOException { return wrappedEntity.getContent(); } @Override public void writeTo(final OutputStream outStream) throws IOException { wrappedEntity.writeTo(outStream); } @Override public boolean isStreaming() { return wrappedEntity.isStreaming(); } @Override public Supplier> getTrailers() { return wrappedEntity.getTrailers(); } @Override public Set getTrailerNames() { return wrappedEntity.getTrailerNames(); } @Override public void close() throws IOException { wrappedEntity.close(); } @Override public String toString() { return "Wrapper [" + wrappedEntity + "]"; } } httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/InputStreamEntity.java0100644 0000000 0000000 00000013432 00000250600 026773 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.util.Args; /** * A streamed, non-repeatable entity that obtains its content from an {@link InputStream}. * * @since 4.0 */ public class InputStreamEntity extends AbstractHttpEntity { private final InputStream content; private final long length; /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
* * @param content The message body contents as an InputStream. * @param contentLength The value for the {@code Content-Length} header for the size of the message body, in bytes. * @param contentType The content-type, may be null. * @param contentEncoding The content encoding string, may be null. */ public InputStreamEntity( final InputStream content, final long contentLength, final ContentType contentType, final String contentEncoding) { super(contentType, contentEncoding); this.content = Args.notNull(content, "Source input stream"); this.length = contentLength; } /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
  • does not define a content encoding.
  • *
* * @param content The message body contents as an InputStream. * @param contentLength The value for the {@code Content-Length} header for the size of the message body, in bytes. * @param contentType The content-type, may be null. */ public InputStreamEntity(final InputStream content, final long contentLength, final ContentType contentType) { this(content, contentLength, contentType, null); } /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
  • does not define a content length.
  • *
  • does not define a content encoding.
  • *
* * @param content The message body contents as an InputStream. * @param contentType The content-type, may be null. */ public InputStreamEntity(final InputStream content, final ContentType contentType) { this(content, -1, contentType, null); } /** * {@inheritDoc} *

* This implementation always returns {@code false}. *

*/ @Override public final boolean isRepeatable() { return false; } /** * {@inheritDoc} * * @return the content length or {@code -1} if unknown. */ @Override public final long getContentLength() { return this.length; } @Override public final InputStream getContent() throws IOException { return this.content; } /** * Writes bytes from the {@code InputStream} this entity was constructed * with to an {@code OutputStream}. The content length * determines how many bytes are written. If the length is unknown ({@code -1}), the * stream will be completely consumed (to the end of the stream). * */ @Override public final void writeTo(final OutputStream outStream) throws IOException { Args.notNull(outStream, "Output stream"); try (final InputStream inStream = this.content) { final byte[] buffer = new byte[OUTPUT_BUFFER_SIZE]; int readLen; if (this.length < 0) { // consume until EOF while ((readLen = inStream.read(buffer)) != -1) { outStream.write(buffer, 0, readLen); } } else { // consume no more than length long remaining = this.length; while (remaining > 0) { readLen = inStream.read(buffer, 0, (int) Math.min(OUTPUT_BUFFER_SIZE, remaining)); if (readLen == -1) { break; } outStream.write(buffer, 0, readLen); remaining -= readLen; } } } } /** * {@inheritDoc} *

* This implementation always returns {@code true}. *

*/ @Override public final boolean isStreaming() { return true; } @Override public final void close() throws IOException { content.close(); } } httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/NullEntity.java0100644 0000000 0000000 00000005636 00000250600 025441 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Collections; import java.util.List; import java.util.Set; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; /** * An empty entity with no content-type. This type may be used for convenience * in place of an empty {@link ByteArrayEntity}. * * @since 5.1 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public final class NullEntity implements HttpEntity { public static final NullEntity INSTANCE = new NullEntity(); private NullEntity() { } @Override public boolean isRepeatable() { return true; } @Override public InputStream getContent() throws IOException, UnsupportedOperationException { return EmptyInputStream.INSTANCE; } @Override public void writeTo(final OutputStream outStream) throws IOException { } @Override public boolean isStreaming() { return false; } @Override public Supplier> getTrailers() { return null; } @Override public void close() throws IOException { } @Override public long getContentLength() { return 0; } @Override public String getContentType() { return null; } @Override public String getContentEncoding() { return null; } @Override public boolean isChunked() { return false; } @Override public Set getTrailerNames() { return Collections.emptySet(); } } httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/PathEntity.java0100644 0000000 0000000 00000007753 00000250600 025425 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.util.Args; /** * A self contained, repeatable entity that obtains its content from a path. *

* This class contains {@link ThreadingBehavior#IMMUTABLE_CONDITIONAL immutable attributes} but subclasses may contain * additional immutable or mutable attributes. *

*/ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class PathEntity extends AbstractHttpEntity { private final Path path; /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
* * @param path The message body contents will be set from this path. * @param contentType The content-type, may be null. * @param contentEncoding The content encoding string, may be null. */ public PathEntity(final Path path, final ContentType contentType, final String contentEncoding) { super(contentType, contentEncoding); this.path = Args.notNull(path, "Path"); } /** * Constructs a new instance with the given attributes kept as immutable. *

* The new instance: *

*
    *
  • is not chunked.
  • *
  • does not define a content encoding.
  • *
* * @param path The message body contents will be set from this path. * @param contentType The content-type, may be null. */ public PathEntity(final Path path, final ContentType contentType) { super(contentType, null); this.path = Args.notNull(path, "Path"); } /** * {@inheritDoc} *

* This implementation always returns {@code false}. *

*/ @Override public final boolean isRepeatable() { return true; } @Override public final long getContentLength() { try { return Files.size(this.path); } catch (final IOException e) { throw new IllegalStateException(e); } } @Override public final InputStream getContent() throws IOException { return Files.newInputStream(path); } /** * {@inheritDoc} *

* This implementation always returns {@code false}. *

*/ @Override public final boolean isStreaming() { return false; } /** * {@inheritDoc} *

* This implementation is a no-op. *

*/ @Override public final void close() throws IOException { // do nothing } } httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/SerializableEntity.java0100644 0000000 0000000 00000010566 00000250600 027133 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.util.Args; /** * A streamed entity that obtains its content from a {@link Serializable}. *

* This class contains {@link ThreadingBehavior#IMMUTABLE_CONDITIONAL immutable attributes} but subclasses may contain * additional immutable or mutable attributes. *

* * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class SerializableEntity extends AbstractHttpEntity { private final Serializable serializable; /** * Creates new instance of this class. *

* The new instance: *

*
    *
  • is not chunked.
  • *
* * @param serializable the serializable object. * @param contentType the content type. * @param contentEncoding the content encoding. */ public SerializableEntity( final Serializable serializable, final ContentType contentType, final String contentEncoding) { super(contentType, contentEncoding); this.serializable = Args.notNull(serializable, "Source object"); } /** * Creates new instance of this class. *

* The new instance: *

*
    *
  • is not chunked.
  • *
  • does not define a content encoding.
  • *
* * @param serializable the serializable object. * @param contentType the content type. */ public SerializableEntity(final Serializable serializable, final ContentType contentType) { this(serializable, contentType, null); } @Override public final InputStream getContent() throws IOException, IllegalStateException { final ByteArrayOutputStream buf = new ByteArrayOutputStream(); writeTo(buf); return new ByteArrayInputStream(buf.toByteArray()); } /** * {@inheritDoc} *

* This implementation always returns {@code -1}. *

*/ @Override public final long getContentLength() { return -1; } /** * {@inheritDoc} *

* This implementation always returns {@code true}. *

*/ @Override public final boolean isRepeatable() { return true; } /** * {@inheritDoc} *

* This implementation always returns {@code false}. *

*/ @Override public final boolean isStreaming() { return false; } @Override public final void writeTo(final OutputStream outStream) throws IOException { Args.notNull(outStream, "Output stream"); final ObjectOutputStream out = new ObjectOutputStream(outStream); out.writeObject(this.serializable); out.flush(); } /** * {@inheritDoc} *

* This implementation is a no-op. *

*/ @Override public final void close() throws IOException { } } httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/StringEntity.java0100644 0000000 0000000 00000016566 00000250600 026001 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.util.Args; /** * A self contained, repeatable entity that obtains its content from a {@link String}. *

* This class contains {@link ThreadingBehavior#IMMUTABLE immutable attributes} but subclasses may contain * additional immutable or mutable attributes. *

* * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class StringEntity extends AbstractHttpEntity { private final byte[] content; /** * Constructs a StringEntity with the specified content and content type. * * @param string The content to be used. Not {@code null}. * @param contentType The content type to be used. May be {@code null}, in which case the default MIME type {@link ContentType#TEXT_PLAIN} is assumed. * @param contentEncoding The content encoding string, may be null. * @param chunked Whether this entity should be chunked. * @throws NullPointerException Thrown if string is null. * @since 5.0 */ public StringEntity( final String string, final ContentType contentType, final String contentEncoding, final boolean chunked) { super(contentType, contentEncoding, chunked); Args.notNull(string, "Source string"); final Charset charset = ContentType.getCharset(contentType, StandardCharsets.UTF_8); this.content = string.getBytes(charset); } /** * Constructs a StringEntity with the specified content and content type. *

* The new instance: *

*
    *
  • does not define a content encoding.
  • *
* * @param string The content to be used. Not {@code null}. * @param contentType The content type to be used. May be {@code null}, in which case the default MIME type {@link ContentType#TEXT_PLAIN} is assumed. * @param chunked Whether this entity should be chunked. * @throws NullPointerException Thrown if string is null. */ public StringEntity(final String string, final ContentType contentType, final boolean chunked) { this(string, contentType, null, chunked); } /** * Constructs a StringEntity with the specified content and content type. *

* The new instance: *

*
    *
  • is not chunked.
  • *
  • does not define a content encoding.
  • *
* * @param string The content to be used. Not {@code null}. * @param contentType The content type to be used. May be {@code null}, in which case the default MIME type {@link ContentType#TEXT_PLAIN} is assumed. * @throws NullPointerException Thrown if string is null. */ public StringEntity(final String string, final ContentType contentType) { this(string, contentType, null, false); } /** * Constructs a StringEntity with the specified content and charset. The MIME type defaults to "text/plain". *

* The new instance: *

*
    *
  • is not chunked.
  • *
  • sets the content type to {@code "text/plain"} and the given Charset.
  • *
* * @param string The content to be used. Not {@code null}. * @param charset The character set to be used. May be {@code null}, in which case the default is {@link StandardCharsets#UTF_8} is assumed. * @throws NullPointerException Thrown if string is null. * @since 4.2 */ public StringEntity(final String string, final Charset charset) { this(string, ContentType.TEXT_PLAIN.withCharset(charset)); } /** * Constructs a StringEntity with the specified content and content type. *

* The new instance: *

*
    *
  • sets the content type to {@code "text/plain"} and the given Charset.
  • *
* * @param string The content to be used. Not {@code null}. * @param charset The character set to be used. May be {@code null}, in which case the default is {@link StandardCharsets#UTF_8} is assumed. * @param chunked Whether this entity should be chunked. * @throws NullPointerException Thrown if string is null. */ public StringEntity(final String string, final Charset charset, final boolean chunked) { this(string, ContentType.TEXT_PLAIN.withCharset(charset), chunked); } /** * Constructs a StringEntity with the specified content and content type. *

* The new instance: *

*
    *
  • is not chunked.
  • *
  • sets the content type to {@code "text/plain"} and the given Charset.
  • *
* * @param string The content to be used. Not {@code null}. * @throws NullPointerException Thrown if string is null. */ public StringEntity(final String string) { this(string, ContentType.DEFAULT_TEXT); } /** * {@inheritDoc} *

* This implementation always returns {@code true}. *

*/ @Override public final boolean isRepeatable() { return true; } @Override public final long getContentLength() { return this.content.length; } @Override public final InputStream getContent() throws IOException { return new ByteArrayInputStream(this.content); } @Override public final void writeTo(final OutputStream outStream) throws IOException { Args.notNull(outStream, "Output stream"); outStream.write(this.content); outStream.flush(); } /** * {@inheritDoc} *

* This implementation always returns {@code false}. *

*/ @Override public final boolean isStreaming() { return false; } /** * {@inheritDoc} *

* This implementation is a no-op. *

*/ @Override public final void close() throws IOException { // nothing to do } } httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/package-info.java0100644 0000000 0000000 00000002434 00000250600 025647 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * HTTP message entity APIs based on the classic (blocking) I/O model. */ package org.apache.hc.core5.http.io.entity; httpcore5/src/main/java/org/apache/hc/core5/http/io/ssl/0040755 0000000 0000000 00000000000 00000250600 021745 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/io/ssl/DefaultTlsSetupHandler.java0100644 0000000 0000000 00000004517 00000250600 027202 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.ssl; import javax.net.ssl.SSLParameters; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.ssl.TLS; import org.apache.hc.core5.http.ssl.TlsCiphers; /** * Default TLS session setup handler. * * @since 5.0 */ public final class DefaultTlsSetupHandler implements Callback { public final static DefaultTlsSetupHandler SERVER = new DefaultTlsSetupHandler(false); public final static DefaultTlsSetupHandler CLIENT = new DefaultTlsSetupHandler(true); private final boolean client; public DefaultTlsSetupHandler() { this.client = false; } /** * @since 5.3 */ public DefaultTlsSetupHandler(final boolean client) { this.client = client; } @Override public void execute(final SSLParameters sslParameters) { sslParameters.setProtocols(TLS.excludeWeak(sslParameters.getProtocols())); sslParameters.setCipherSuites(TlsCiphers.excludeWeak(sslParameters.getCipherSuites())); if (client) { sslParameters.setEndpointIdentificationAlgorithm(URIScheme.HTTPS.id); } } } httpcore5/src/main/java/org/apache/hc/core5/http/io/ssl/SSLSessionVerifier.java0100644 0000000 0000000 00000003037 00000250600 026311 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.ssl; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSession; import org.apache.hc.core5.http.HttpHost; /** * Callback interface that can be used to customize TLS/SSL session verification. * * @since 5.0 */ public interface SSLSessionVerifier { void verify(HttpHost endpoint, SSLSession sslSession) throws SSLException; } httpcore5/src/main/java/org/apache/hc/core5/http/io/support/0040755 0000000 0000000 00000000000 00000250600 022660 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/io/support/AbstractHttpServerAuthFilter.java0100644 0000000 0000000 00000015407 00000250600 031311 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.support; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.HttpFilterChain; import org.apache.hc.core5.http.io.HttpFilterHandler; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.net.URIAuthority; /** * Abstract HTTP request filter that implements standard HTTP authentication handshake. * * @param authorization token representation. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.STATELESS) public abstract class AbstractHttpServerAuthFilter implements HttpFilterHandler { private final boolean respondImmediately; protected AbstractHttpServerAuthFilter(final boolean respondImmediately) { this.respondImmediately = respondImmediately; } /** * Parses authorization header value into an authentication token sent by the client * as a response to an authentication challenge. * * @param authorizationValue the authorization header value. * @param context the actual execution context. * @return authorization token * @throws HttpException in case of an HTTP protocol violation. */ protected abstract T parseChallengeResponse(String authorizationValue, HttpContext context) throws HttpException; /** * Authenticates the client using the authentication token sent by the client * as a response to an authentication challenge. * * @param challengeResponse the authentication token sent by the client * as a response to an authentication challenge. * @param authority the URI authority. * @param requestUri the request URI. * @param context the actual execution context. * @return {@code true} if the client could be successfully authenticated {@code false} otherwise. */ protected abstract boolean authenticate(T challengeResponse, URIAuthority authority, String requestUri, HttpContext context); /** * Generates an authentication challenge in case of unsuccessful authentication. * * @param challengeResponse the authentication token sent by the client * as a response to an authentication challenge * or {@code null} if the client has not sent any. * @param authority the URI authority. * @param requestUri the request URI. * @param context the actual execution context. * @return an authorization challenge value. */ protected abstract String generateChallenge(T challengeResponse, URIAuthority authority, String requestUri, HttpContext context); /** * Generates response body for UNAUTHORIZED response. * * @param unauthorized the response to return as a result of authentication failure. * @return the response content entity. */ protected HttpEntity generateResponseContent(final HttpResponse unauthorized) { return new StringEntity("Unauthorized"); } @Override public final void handle( final ClassicHttpRequest request, final HttpFilterChain.ResponseTrigger responseTrigger, final HttpContext context, final HttpFilterChain chain) throws HttpException, IOException { final Header h = request.getFirstHeader(HttpHeaders.AUTHORIZATION); final T challengeResponse = h != null ? parseChallengeResponse(h.getValue(), context) : null; final URIAuthority authority = request.getAuthority(); final String requestUri = request.getRequestUri(); final boolean authenticated = authenticate(challengeResponse, authority, requestUri, context); final Header expect = request.getFirstHeader(HttpHeaders.EXPECT); final boolean expectContinue = expect != null && HeaderElements.CONTINUE.equalsIgnoreCase(expect.getValue()); if (authenticated) { if (expectContinue) { responseTrigger.sendInformation(new BasicClassicHttpResponse(HttpStatus.SC_CONTINUE)); } chain.proceed(request, responseTrigger, context); } else { final ClassicHttpResponse unauthorized = new BasicClassicHttpResponse(HttpStatus.SC_UNAUTHORIZED); unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, generateChallenge(challengeResponse, authority, requestUri, context)); final HttpEntity responseContent = generateResponseContent(unauthorized); unauthorized.setEntity(responseContent); if (respondImmediately || expectContinue || request.getEntity() == null) { // Respond immediately responseTrigger.submitResponse(unauthorized); // Consume request body later EntityUtils.consume(request.getEntity()); } else { // Consume request body first EntityUtils.consume(request.getEntity()); // Respond later responseTrigger.submitResponse(unauthorized); } } } } ././@LongLink0100644 0000000 0000000 00000000145 00000250600 011611 Lustar 0000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/io/support/BasicHttpServerExpectationDecorator.javahttpcore5/src/main/java/org/apache/hc/core5/http/io/support/BasicHttpServerExpectationDecorator.java0100644 0000000 0000000 00000007452 00000250600 032647 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.support; import java.io.IOException; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.HttpServerRequestHandler; import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.http.support.ExpectSupport; import org.apache.hc.core5.http.support.Expectation; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * {@link HttpServerRequestHandler} implementation that adds support * for the Expect-Continue handshake to an existing * {@link HttpServerRequestHandler}. * * @since 5.0 */ public class BasicHttpServerExpectationDecorator implements HttpServerRequestHandler { private final HttpServerRequestHandler requestHandler; public BasicHttpServerExpectationDecorator(final HttpServerRequestHandler requestHandler) { this.requestHandler = Args.notNull(requestHandler, "Request handler"); } /** * Verifies the HTTP request and decides whether it meets server expectations and the request * processing can continue. * * @param request the incoming HTTP request. * @param context the actual execution context. * @return {@code null} if the request meets expectations or a final HTTP response * with an error status representing the cause of expectation failure. */ protected ClassicHttpResponse verify(final ClassicHttpRequest request, final HttpContext context) { return null; } @Override public final void handle( final ClassicHttpRequest request, final ResponseTrigger responseTrigger, final HttpContext context) throws HttpException, IOException { final Expectation expectation = ExpectSupport.parse(request, request.getEntity()); if (expectation == Expectation.CONTINUE) { final ClassicHttpResponse response = verify(request, context); if (response == null) { responseTrigger.sendInformation(new BasicClassicHttpResponse(HttpStatus.SC_CONTINUE)); } else { responseTrigger.submitResponse(response); return; } } else if (expectation == Expectation.UNKNOWN) { final ClassicHttpResponse expectationFailed = new BasicClassicHttpResponse(HttpStatus.SC_EXPECTATION_FAILED); responseTrigger.submitResponse(expectationFailed); return; } requestHandler.handle(request, responseTrigger, context); } } httpcore5/src/main/java/org/apache/hc/core5/http/io/support/BasicHttpServerRequestHandler.java0100644 0000000 0000000 00000006631 00000250600 031445 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.support; import java.io.IOException; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequestMapper; import org.apache.hc.core5.http.HttpResponseFactory; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.impl.io.DefaultClassicHttpResponseFactory; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.HttpServerRequestHandler; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * Basic {@link HttpServerRequestHandler} implementation that makes use of * {@link HttpRequestMapper} to dispatch the request to a particular * {@link HttpRequestHandler} for processing. * * @since 5.0 */ public class BasicHttpServerRequestHandler implements HttpServerRequestHandler { private final HttpRequestMapper handlerMapper; private final HttpResponseFactory responseFactory; public BasicHttpServerRequestHandler( final HttpRequestMapper handlerMapper, final HttpResponseFactory responseFactory) { this.handlerMapper = Args.notNull(handlerMapper, "Handler mapper"); this.responseFactory = responseFactory != null ? responseFactory : DefaultClassicHttpResponseFactory.INSTANCE; } public BasicHttpServerRequestHandler(final HttpRequestMapper handlerMapper) { this(handlerMapper, null); } @Override public void handle( final ClassicHttpRequest request, final ResponseTrigger responseTrigger, final HttpContext context) throws HttpException, IOException { final ClassicHttpResponse response = responseFactory.newHttpResponse(HttpStatus.SC_OK); final HttpRequestHandler handler = handlerMapper != null ? handlerMapper.resolve(request, context) : null; if (handler != null) { handler.handle(request, response, context); } else { response.setCode(HttpStatus.SC_NOT_IMPLEMENTED); } responseTrigger.submitResponse(response); } } httpcore5/src/main/java/org/apache/hc/core5/http/io/support/ClassicRequestBuilder.java0100644 0000000 0000000 00000034274 00000250600 027773 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.support; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.io.entity.ByteArrayEntity; import org.apache.hc.core5.http.io.entity.HttpEntities; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.support.AbstractRequestBuilder; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.net.URIBuilder; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TextUtils; /** * Builder for {@link ClassicHttpRequest} instances. *

* Please note that this class treats parameters differently depending on composition * of the request: if the request has a content entity explicitly set with * {@link #setEntity(HttpEntity)} or it is not an entity enclosing method * (such as POST or PUT), parameters will be added to the query component * of the request URI. Otherwise, parameters will be added as a URL encoded entity. *

* * @since 5.0 */ public class ClassicRequestBuilder extends AbstractRequestBuilder { private HttpEntity entity; ClassicRequestBuilder(final String method) { super(method); } ClassicRequestBuilder(final Method method) { super(method); } ClassicRequestBuilder(final String method, final URI uri) { super(method, uri); } ClassicRequestBuilder(final Method method, final URI uri) { super(method, uri); } ClassicRequestBuilder(final Method method, final String uri) { super(method, uri); } ClassicRequestBuilder(final String method, final String uri) { super(method, uri); } public static ClassicRequestBuilder create(final String method) { Args.notBlank(method, "HTTP method"); return new ClassicRequestBuilder(method); } public static ClassicRequestBuilder get() { return new ClassicRequestBuilder(Method.GET); } public static ClassicRequestBuilder get(final URI uri) { return new ClassicRequestBuilder(Method.GET, uri); } public static ClassicRequestBuilder get(final String uri) { return new ClassicRequestBuilder(Method.GET, uri); } public static ClassicRequestBuilder head() { return new ClassicRequestBuilder(Method.HEAD); } public static ClassicRequestBuilder head(final URI uri) { return new ClassicRequestBuilder(Method.HEAD, uri); } public static ClassicRequestBuilder head(final String uri) { return new ClassicRequestBuilder(Method.HEAD, uri); } /** * Initializes a new {@link ClassicRequestBuilder} instance for the {@code QUERY} method. * * @see Method#QUERY for more information regarding the properties of the {@code QUERY} method. * * @since 5.4 */ @Experimental public static ClassicRequestBuilder query() { return new ClassicRequestBuilder(Method.QUERY); } /** * Initializes a new {@link ClassicRequestBuilder} instance for the {@code QUERY} method. * * @param uri the request URI. * @see Method#QUERY for more information regarding the properties of the {@code QUERY} method. * * @since 5.4 */ @Experimental public static ClassicRequestBuilder query(final URI uri) { return new ClassicRequestBuilder(Method.QUERY, uri); } /** * Initializes a new {@link ClassicRequestBuilder} instance for the {@code QUERY} method. * * @param uri the request URI. * @see Method#QUERY for more information regarding the properties of the {@code QUERY} method. * * @since 5.4 */ @Experimental public static ClassicRequestBuilder query(final String uri) { return new ClassicRequestBuilder(Method.QUERY, uri); } public static ClassicRequestBuilder patch() { return new ClassicRequestBuilder(Method.PATCH); } public static ClassicRequestBuilder patch(final URI uri) { return new ClassicRequestBuilder(Method.PATCH, uri); } public static ClassicRequestBuilder patch(final String uri) { return new ClassicRequestBuilder(Method.PATCH, uri); } public static ClassicRequestBuilder post() { return new ClassicRequestBuilder(Method.POST); } public static ClassicRequestBuilder post(final URI uri) { return new ClassicRequestBuilder(Method.POST, uri); } public static ClassicRequestBuilder post(final String uri) { return new ClassicRequestBuilder(Method.POST, uri); } public static ClassicRequestBuilder put() { return new ClassicRequestBuilder(Method.PUT); } public static ClassicRequestBuilder put(final URI uri) { return new ClassicRequestBuilder(Method.PUT, uri); } public static ClassicRequestBuilder put(final String uri) { return new ClassicRequestBuilder(Method.PUT, uri); } public static ClassicRequestBuilder delete() { return new ClassicRequestBuilder(Method.DELETE); } public static ClassicRequestBuilder delete(final URI uri) { return new ClassicRequestBuilder(Method.DELETE, uri); } public static ClassicRequestBuilder delete(final String uri) { return new ClassicRequestBuilder(Method.DELETE, uri); } public static ClassicRequestBuilder trace() { return new ClassicRequestBuilder(Method.TRACE); } public static ClassicRequestBuilder trace(final URI uri) { return new ClassicRequestBuilder(Method.TRACE, uri); } public static ClassicRequestBuilder trace(final String uri) { return new ClassicRequestBuilder(Method.TRACE, uri); } public static ClassicRequestBuilder options() { return new ClassicRequestBuilder(Method.OPTIONS); } public static ClassicRequestBuilder options(final URI uri) { return new ClassicRequestBuilder(Method.OPTIONS, uri); } public static ClassicRequestBuilder options(final String uri) { return new ClassicRequestBuilder(Method.OPTIONS, uri); } /** * @since 5.1 */ public static ClassicRequestBuilder copy(final ClassicHttpRequest request) { Args.notNull(request, "HTTP request"); final ClassicRequestBuilder builder = new ClassicRequestBuilder(request.getMethod()); builder.digest(request); return builder; } /** * @since 5.4 */ public static ClassicRequestBuilder copy(final HttpRequest request) { Args.notNull(request, "HTTP request"); final ClassicRequestBuilder builder = new ClassicRequestBuilder(request.getMethod()); builder.digest(request); return builder; } protected void digest(final ClassicHttpRequest request) { super.digest(request); setEntity(request.getEntity()); } @Override public ClassicRequestBuilder setVersion(final ProtocolVersion version) { super.setVersion(version); return this; } @Override public ClassicRequestBuilder setUri(final URI uri) { super.setUri(uri); return this; } @Override public ClassicRequestBuilder setUri(final String uri) { super.setUri(uri); return this; } @Override public ClassicRequestBuilder setScheme(final String scheme) { super.setScheme(scheme); return this; } @Override public ClassicRequestBuilder setAuthority(final URIAuthority authority) { super.setAuthority(authority); return this; } /** * @since 5.1 */ @Override public ClassicRequestBuilder setHttpHost(final HttpHost httpHost) { super.setHttpHost(httpHost); return this; } @Override public ClassicRequestBuilder setPath(final String path) { super.setPath(path); return this; } @Override public ClassicRequestBuilder setHeaders(final Header... headers) { super.setHeaders(headers); return this; } @Override public ClassicRequestBuilder addHeader(final Header header) { super.addHeader(header); return this; } @Override public ClassicRequestBuilder addHeader(final String name, final String value) { super.addHeader(name, value); return this; } @Override public ClassicRequestBuilder removeHeader(final Header header) { super.removeHeader(header); return this; } @Override public ClassicRequestBuilder removeHeaders(final String name) { super.removeHeaders(name); return this; } @Override public ClassicRequestBuilder setHeader(final Header header) { super.setHeader(header); return this; } @Override public ClassicRequestBuilder setHeader(final String name, final String value) { super.setHeader(name, value); return this; } @Override public ClassicRequestBuilder setCharset(final Charset charset) { super.setCharset(charset); return this; } @Override public ClassicRequestBuilder addParameter(final NameValuePair nvp) { super.addParameter(nvp); return this; } @Override public ClassicRequestBuilder addParameter(final String name, final String value) { super.addParameter(name, value); return this; } @Override public ClassicRequestBuilder addParameters(final NameValuePair... nvps) { super.addParameters(nvps); return this; } @Override public ClassicRequestBuilder setAbsoluteRequestUri(final boolean absoluteRequestUri) { super.setAbsoluteRequestUri(absoluteRequestUri); return this; } public HttpEntity getEntity() { return entity; } public ClassicRequestBuilder setEntity(final HttpEntity entity) { this.entity = entity; return this; } public ClassicRequestBuilder setEntity(final String content, final ContentType contentType) { this.entity = new StringEntity(content, contentType); return this; } public ClassicRequestBuilder setEntity(final String content) { this.entity = new StringEntity(content); return this; } public ClassicRequestBuilder setEntity(final byte[] content, final ContentType contentType) { this.entity = new ByteArrayEntity(content, contentType); return this; } @Override public ClassicHttpRequest build() { String path = getPath(); if (TextUtils.isEmpty(path)) { path = "/"; } HttpEntity entityCopy = this.entity; final String method = getMethod(); final List parameters = getParameters(); if (parameters != null && !parameters.isEmpty()) { if (entityCopy == null && (Method.POST.isSame(method) || Method.PUT.isSame(method))) { entityCopy = HttpEntities.createUrlEncoded(parameters, getCharset()); } else { try { final URI uri = new URIBuilder(path) .setCharset(getCharset()) .addParameters(parameters) .build(); path = uri.toASCIIString(); } catch (final URISyntaxException ex) { // should never happen } } } if (entityCopy != null && Method.TRACE.isSame(method)) { throw new IllegalStateException(Method.TRACE + " requests may not include an entity"); } final BasicClassicHttpRequest result = new BasicClassicHttpRequest(method, getScheme(), getAuthority(), path); result.setVersion(getVersion()); result.setHeaders(getHeaders()); result.setEntity(entityCopy); result.setAbsoluteRequestUri(isAbsoluteRequestUri()); return result; } @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("ClassicRequestBuilder [method="); builder.append(getMethod()); builder.append(", scheme="); builder.append(getScheme()); builder.append(", authority="); builder.append(getAuthority()); builder.append(", path="); builder.append(getPath()); builder.append(", parameters="); builder.append(getParameters()); builder.append(", headerGroup="); builder.append(Arrays.toString(getHeaders())); builder.append(", entity="); builder.append(entity != null ? entity.getClass() : null); builder.append("]"); return builder.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/io/support/ClassicResponseBuilder.java0100644 0000000 0000000 00000013222 00000250600 030127 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.support; import java.util.Arrays; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.io.entity.ByteArrayEntity; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.http.support.AbstractResponseBuilder; import org.apache.hc.core5.util.Args; /** * Builder for {@link ClassicHttpResponse} instances. * * @since 5.0 */ public class ClassicResponseBuilder extends AbstractResponseBuilder { private HttpEntity entity; ClassicResponseBuilder(final int status) { super(status); } public static ClassicResponseBuilder create(final int status) { Args.checkRange(status, 100, 599, "HTTP status code"); return new ClassicResponseBuilder(status); } /** * @since 5.1 */ public static ClassicResponseBuilder copy(final ClassicHttpResponse response) { Args.notNull(response, "HTTP response"); final ClassicResponseBuilder builder = new ClassicResponseBuilder(response.getCode()); builder.digest(response); return builder; } /** * @since 5.4 */ public static ClassicResponseBuilder copy(final HttpResponse response) { Args.notNull(response, "HTTP response"); final ClassicResponseBuilder builder = new ClassicResponseBuilder(response.getCode()); builder.digest(response); return builder; } protected void digest(final ClassicHttpResponse response) { super.digest(response); setEntity(response.getEntity()); } @Override public ClassicResponseBuilder setVersion(final ProtocolVersion version) { super.setVersion(version); return this; } @Override public ClassicResponseBuilder setHeaders(final Header... headers) { super.setHeaders(headers); return this; } @Override public ClassicResponseBuilder addHeader(final Header header) { super.addHeader(header); return this; } @Override public ClassicResponseBuilder addHeader(final String name, final String value) { super.addHeader(name, value); return this; } @Override public ClassicResponseBuilder removeHeader(final Header header) { super.removeHeader(header); return this; } @Override public ClassicResponseBuilder removeHeaders(final String name) { super.removeHeaders(name); return this; } @Override public ClassicResponseBuilder setHeader(final Header header) { super.setHeader(header); return this; } @Override public ClassicResponseBuilder setHeader(final String name, final String value) { super.setHeader(name, value); return this; } public HttpEntity getEntity() { return entity; } public ClassicResponseBuilder setEntity(final HttpEntity entity) { this.entity = entity; return this; } public ClassicResponseBuilder setEntity(final String content, final ContentType contentType) { this.entity = new StringEntity(content, contentType); return this; } public ClassicResponseBuilder setEntity(final String content) { this.entity = new StringEntity(content); return this; } public ClassicResponseBuilder setEntity(final byte[] content, final ContentType contentType) { this.entity = new ByteArrayEntity(content, contentType); return this; } @Override public ClassicHttpResponse build() { final BasicClassicHttpResponse result = new BasicClassicHttpResponse(getStatus()); result.setVersion(getVersion()); result.setHeaders(getHeaders()); result.setEntity(entity); return result; } @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("ClassicResponseBuilder [status="); builder.append(getStatus()); builder.append(", headerGroup="); builder.append(Arrays.toString(getHeaders())); builder.append(", entity="); builder.append(entity != null ? entity.getClass() : null); builder.append("]"); return builder.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/io/support/HttpServerExpectationFilter.java0100644 0000000 0000000 00000011414 00000250600 031201 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.support; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.HttpFilterChain; import org.apache.hc.core5.http.io.HttpFilterHandler; import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.support.ExpectSupport; import org.apache.hc.core5.http.support.Expectation; /** * HttpServerExpectationFilter add support for the Expect-Continue handshake * to the request processing pipeline. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.STATELESS) public class HttpServerExpectationFilter implements HttpFilterHandler { /** * Verifies the HTTP request and decides whether it meets server expectations and the request * processing can continue. * * @param request the incoming HTTP request. * @param context the actual execution context. * @return {@code true} if the request meets expectations or {@code false} otherwise. * @throws HttpException in case of an HTTP protocol violation. */ protected boolean verify(final ClassicHttpRequest request, final HttpContext context) throws HttpException { return true; } /** * Generates response content entity for the final HTTP response with an error status * representing the cause of expectation failure. * * @param expectationFailed the final HTTP response. * @return the content entity for the final HTTP response with an error status * representing the cause of expectation failure. * @throws HttpException in case of an HTTP protocol violation. */ protected HttpEntity generateResponseContent(final HttpResponse expectationFailed) throws HttpException { return null; } @Override public final void handle( final ClassicHttpRequest request, final HttpFilterChain.ResponseTrigger responseTrigger, final HttpContext context, final HttpFilterChain chain) throws HttpException, IOException { final Expectation expectation = ExpectSupport.parse(request, request.getEntity()); if (expectation == Expectation.CONTINUE) { final boolean verified = verify(request, context); if (verified) { responseTrigger.sendInformation(new BasicClassicHttpResponse(HttpStatus.SC_CONTINUE)); } else { final ClassicHttpResponse expectationFailed = new BasicClassicHttpResponse(HttpStatus.SC_EXPECTATION_FAILED); final HttpEntity responseContent = generateResponseContent(expectationFailed); expectationFailed.setEntity(responseContent); responseTrigger.submitResponse(expectationFailed); return; } } else if (expectation == Expectation.UNKNOWN) { final ClassicHttpResponse expectationFailed = new BasicClassicHttpResponse(HttpStatus.SC_EXPECTATION_FAILED); final HttpEntity responseContent = generateResponseContent(expectationFailed); expectationFailed.setEntity(responseContent); responseTrigger.submitResponse(expectationFailed); return; } chain.proceed(request, responseTrigger, context); } } httpcore5/src/main/java/org/apache/hc/core5/http/io/support/HttpServerFilterChainElement.java0100644 0000000 0000000 00000004763 00000250600 031263 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.support; import java.io.IOException; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.io.HttpFilterChain; import org.apache.hc.core5.http.io.HttpFilterHandler; import org.apache.hc.core5.http.protocol.HttpContext; /** * An element in a request processing chain. * * @since 5.0 */ public final class HttpServerFilterChainElement { private final HttpFilterHandler handler; private final HttpServerFilterChainElement next; private final HttpFilterChain filterChain; public HttpServerFilterChainElement(final HttpFilterHandler handler, final HttpServerFilterChainElement next) { this.handler = handler; this.next = next; this.filterChain = next != null ? next::handle : null; } public void handle( final ClassicHttpRequest request, final HttpFilterChain.ResponseTrigger responseTrigger, final HttpContext context) throws IOException, HttpException { handler.handle(request, responseTrigger, context, filterChain); } @Override public String toString() { return "{" + "handler=" + handler.getClass() + ", next=" + (next != null ? next.handler.getClass() : "null") + '}'; } }././@LongLink0100644 0000000 0000000 00000000145 00000250600 011611 Lustar 0000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/io/support/HttpServerFilterChainRequestHandler.javahttpcore5/src/main/java/org/apache/hc/core5/http/io/support/HttpServerFilterChainRequestHandler.java0100644 0000000 0000000 00000005365 00000250600 032617 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.support; import java.io.IOException; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.io.HttpFilterChain; import org.apache.hc.core5.http.io.HttpServerRequestHandler; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * {@link HttpServerRequestHandler} implementation that delegates request processing * to a {@link HttpServerFilterChainElement}. * * @since 5.0 */ public class HttpServerFilterChainRequestHandler implements HttpServerRequestHandler { private final HttpServerFilterChainElement filterChain; public HttpServerFilterChainRequestHandler(final HttpServerFilterChainElement filterChain) { this.filterChain = Args.notNull(filterChain, "Filter chain"); } @Override public void handle( final ClassicHttpRequest request, final ResponseTrigger trigger, final HttpContext context) throws HttpException, IOException { filterChain.handle(request, new HttpFilterChain.ResponseTrigger() { @Override public void sendInformation(final ClassicHttpResponse response) throws HttpException, IOException { trigger.sendInformation(response); } @Override public void submitResponse(final ClassicHttpResponse response) throws HttpException, IOException { trigger.submitResponse(response); } }, context); } } httpcore5/src/main/java/org/apache/hc/core5/http/io/support/TerminalServerFilter.java0100644 0000000 0000000 00000006772 00000250600 027644 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.support; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequestMapper; import org.apache.hc.core5.http.HttpResponseFactory; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.impl.io.DefaultClassicHttpResponseFactory; import org.apache.hc.core5.http.io.HttpFilterChain; import org.apache.hc.core5.http.io.HttpFilterHandler; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * {@link HttpFilterHandler} implementation represents a terminal handler * in a request processing pipeline that makes use of {@link HttpRequestMapper} * to dispatch the request to a particular {@link HttpRequestHandler}. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.STATELESS) public final class TerminalServerFilter implements HttpFilterHandler { private final HttpRequestMapper handlerMapper; private final HttpResponseFactory responseFactory; public TerminalServerFilter( final HttpRequestMapper handlerMapper, final HttpResponseFactory responseFactory) { this.handlerMapper = Args.notNull(handlerMapper, "Handler mapper"); this.responseFactory = responseFactory != null ? responseFactory : DefaultClassicHttpResponseFactory.INSTANCE; } @Override public void handle( final ClassicHttpRequest request, final HttpFilterChain.ResponseTrigger responseTrigger, final HttpContext context, final HttpFilterChain chain) throws HttpException, IOException { final ClassicHttpResponse response = responseFactory.newHttpResponse(HttpStatus.SC_OK); final HttpRequestHandler handler = handlerMapper.resolve(request, context); if (handler != null) { handler.handle(request, response, context); } else { response.setCode(HttpStatus.SC_NOT_IMPLEMENTED); } responseTrigger.submitResponse(response); } } httpcore5/src/main/java/org/apache/hc/core5/http/io/support/package-info.java0100644 0000000 0000000 00000002417 00000250600 026050 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Support classes for the classic (blocking) I/O model. */ package org.apache.hc.core5.http.io.support; httpcore5/src/main/java/org/apache/hc/core5/http/message/0040755 0000000 0000000 00000000000 00000250600 022161 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/message/AbstractHeaderElementIterator.java0100644 0000000 0000000 00000010713 00000250600 030723 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.util.Iterator; import java.util.NoSuchElementException; import org.apache.hc.core5.http.FormattedHeader; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.util.Args; /** * {@link Iterator} of {@link org.apache.hc.core5.http.HeaderElement}s. * * @since 5.0 */ abstract class AbstractHeaderElementIterator implements Iterator { private final Iterator headerIt; private T currentElement; private CharSequence buffer; private ParserCursor cursor; /** * Creates a new instance of BasicHeaderElementIterator */ AbstractHeaderElementIterator(final Iterator headerIterator) { this.headerIt = Args.notNull(headerIterator, "Header iterator"); } private void bufferHeaderValue() { this.cursor = null; this.buffer = null; while (this.headerIt.hasNext()) { final Header h = this.headerIt.next(); if (h instanceof FormattedHeader) { this.buffer = ((FormattedHeader) h).getBuffer(); this.cursor = new ParserCursor(0, this.buffer.length()); this.cursor.updatePos(((FormattedHeader) h).getValuePos()); break; } final String value = h.getValue(); if (value != null) { this.buffer = value; this.cursor = new ParserCursor(0, value.length()); break; } } } abstract T parseHeaderElement(CharSequence buf, ParserCursor cursor); private void parseNextElement() { // loop while there are headers left to parse while (this.headerIt.hasNext() || this.cursor != null) { if (this.cursor == null || this.cursor.atEnd()) { // get next header value bufferHeaderValue(); } // Anything buffered? if (this.cursor != null) { // loop while there is data in the buffer while (!this.cursor.atEnd()) { final T e = parseHeaderElement(this.buffer, this.cursor); if (e != null) { // Found something this.currentElement = e; return; } } // if at the end of the buffer if (this.cursor.atEnd()) { // discard it this.cursor = null; this.buffer = null; } } } } @Override public boolean hasNext() { if (this.currentElement == null) { parseNextElement(); } return this.currentElement != null; } @Override public T next() throws NoSuchElementException { if (this.currentElement == null) { parseNextElement(); } if (this.currentElement == null) { throw new NoSuchElementException("No more header elements available"); } final T element = this.currentElement; this.currentElement = null; return element; } @Override public void remove() throws UnsupportedOperationException { throw new UnsupportedOperationException("Remove not supported"); } } httpcore5/src/main/java/org/apache/hc/core5/http/message/AbstractMessageWrapper.java0100644 0000000 0000000 00000007715 00000250600 027444 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.util.Iterator; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpMessage; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.util.Args; /** * Abstract {@link HttpMessage} wrapper. * * @param A {@link HttpMessage} type. */ public abstract class AbstractMessageWrapper implements HttpMessage { private final T message; public AbstractMessageWrapper(final T message) { this.message = Args.notNull(message, "Message"); } @Override public void setVersion(final ProtocolVersion version) { message.setVersion(version); } @Override public ProtocolVersion getVersion() { return message.getVersion(); } @Override public void addHeader(final Header header) { message.addHeader(header); } @Override public void addHeader(final String name, final Object value) { message.addHeader(name, value); } @Override public void setHeader(final Header header) { message.setHeader(header); } @Override public void setHeader(final String name, final Object value) { message.setHeader(name, value); } @Override public void setHeaders(final Header... headers) { message.setHeaders(headers); } @Override public boolean removeHeader(final Header header) { return message.removeHeader(header); } @Override public boolean removeHeaders(final String name) { return message.removeHeaders(name); } @Override public boolean containsHeader(final String name) { return message.containsHeader(name); } @Override public int countHeaders(final String name) { return message.countHeaders(name); } @Override public Header[] getHeaders(final String name) { return message.getHeaders(name); } @Override public Header getHeader(final String name) throws ProtocolException { return message.getHeader(name); } @Override public Header getFirstHeader(final String name) { return message.getFirstHeader(name); } @Override public Header getLastHeader(final String name) { return message.getLastHeader(name); } T getMessage() { return message; } @Override public Header[] getHeaders() { return message.getHeaders(); } @Override public Iterator
headerIterator() { return message.headerIterator(); } @Override public Iterator
headerIterator(final String name) { return message.headerIterator(name); } @Override public String toString() { return message.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicClassicHttpRequest.java0100644 0000000 0000000 00000010146 00000250600 027557 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.net.URIAuthority; import java.net.URI; /** * Basic implementation of {@link ClassicHttpRequest}. * * @since 5.0 */ public class BasicClassicHttpRequest extends BasicHttpRequest implements ClassicHttpRequest { private static final long serialVersionUID = 1L; private HttpEntity entity; /** * Creates request message with the given method, host and request path. * * @param method request method. * @param scheme request scheme. * @param authority request authority. * @param path request path. * * @since 5.1 */ public BasicClassicHttpRequest(final String method, final String scheme, final URIAuthority authority, final String path) { super(method, scheme, authority, path); } /** * Creates request message with the given method and request path. * * @param method request method. * @param path request path. */ public BasicClassicHttpRequest(final String method, final String path) { super(method, path); } /** * Creates request message with the given method, host and request path. * * @param method request method. * @param host request host. * @param path request path. */ public BasicClassicHttpRequest(final String method, final HttpHost host, final String path) { super(method, host, path); } /** * Creates request message with the given method, request URI. * * @param method request method. * @param requestUri request URI. */ public BasicClassicHttpRequest(final String method, final URI requestUri) { super(method, requestUri); } /** * Creates request message with the given method and request path. * * @param method request method. * @param path request path. */ public BasicClassicHttpRequest(final Method method, final String path) { super(method, path); } /** * Creates request message with the given method, host and request path. * * @param method request method. * @param host request host. * @param path request path. */ public BasicClassicHttpRequest(final Method method, final HttpHost host, final String path) { super(method, host, path); } /** * Creates request message with the given method, request URI. * * @param method request method. * @param requestUri request URI. */ public BasicClassicHttpRequest(final Method method, final URI requestUri) { super(method, requestUri); } @Override public HttpEntity getEntity() { return this.entity; } @Override public void setEntity(final HttpEntity entity) { this.entity = entity; } } httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicClassicHttpResponse.java0100644 0000000 0000000 00000006057 00000250600 027733 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.io.IOException; import java.util.Locale; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.ReasonPhraseCatalog; import org.apache.hc.core5.io.Closer; /** * Basic implementation of {@link ClassicHttpResponse}. * * @since 5.0 */ public class BasicClassicHttpResponse extends BasicHttpResponse implements ClassicHttpResponse { private static final long serialVersionUID = 1L; private HttpEntity entity; /** * Creates a new response. * * @param code the status code * @param catalog the reason phrase catalog, or * {@code null} to disable automatic * reason phrase lookup * @param locale the locale for looking up reason phrases, or * {@code null} for the system locale */ public BasicClassicHttpResponse(final int code, final ReasonPhraseCatalog catalog, final Locale locale) { super(code, catalog, locale); } /** * Creates a new response. * * @param code the status code of the response * @param reasonPhrase the reason phrase to the status code, or {@code null} */ public BasicClassicHttpResponse(final int code, final String reasonPhrase) { super(code, reasonPhrase); } /** * Creates a new response. * * @param code the status code of the response */ public BasicClassicHttpResponse(final int code) { super(code); } @Override public HttpEntity getEntity() { return this.entity; } @Override public void setEntity(final HttpEntity entity) { this.entity = entity; } @Override public void close() throws IOException { Closer.close(entity); } } httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeader.java0100644 0000000 0000000 00000006227 00000250600 025162 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.io.Serializable; import java.util.Objects; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.util.Args; /** * Immutable {@link Header}. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class BasicHeader implements Header, Cloneable, Serializable { private static final long serialVersionUID = -5427236326487562174L; private final String name; private final boolean sensitive; private final String value; /** * Default constructor * * @param name the header name * @param value the header value, taken as the value's {@link #toString()}. */ public BasicHeader(final String name, final Object value) { this(name, value, false); } /** * Constructor with sensitivity flag * * @param name the header name * @param value the header value, taken as the value's {@link #toString()}. * @param sensitive sensitive flag * * @since 5.0 */ public BasicHeader(final String name, final Object value, final boolean sensitive) { super(); this.name = Args.notNull(name, "Name"); this.value = Objects.toString(value, null); this.sensitive = sensitive; } @Override public String getName() { return name; } @Override public String getValue() { return value; } @Override public boolean isSensitive() { return this.sensitive; } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append(this.getName()).append(": "); if (this.getValue() != null) { buf.append(this.getValue()); } return buf.toString(); } @Override public BasicHeader clone() throws CloneNotSupportedException { return (BasicHeader) super.clone(); } } httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderElement.java0100644 0000000 0000000 00000007711 00000250600 026473 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.http.HeaderElement; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.util.Args; /** * Basic implementation of {@link HeaderElement} * * @since 4.0 */ public class BasicHeaderElement implements HeaderElement { private static final NameValuePair[] EMPTY_NAME_VALUE_PAIR_ARRAY = {}; private final String name; private final String value; private final NameValuePair[] parameters; /** * Constructor with name, value and parameters. * * @param name header element name * @param value header element value. May be {@code null} * @param parameters header element parameters. May be {@code null}. * Parameters are copied by reference, not by value */ public BasicHeaderElement( final String name, final String value, final NameValuePair... parameters) { super(); this.name = Args.notNull(name, "Name"); this.value = value; if (parameters != null) { this.parameters = parameters; } else { this.parameters = EMPTY_NAME_VALUE_PAIR_ARRAY; } } /** * Constructor with name and value. * * @param name header element name * @param value header element value. May be {@code null} */ public BasicHeaderElement(final String name, final String value) { this(name, value, (NameValuePair[]) null); } @Override public String getName() { return this.name; } @Override public String getValue() { return this.value; } @Override public NameValuePair[] getParameters() { return this.parameters.clone(); } @Override public int getParameterCount() { return this.parameters.length; } @Override public NameValuePair getParameter(final int index) { // ArrayIndexOutOfBoundsException is appropriate return this.parameters[index]; } @Override public NameValuePair getParameterByName(final String name) { Args.notNull(name, "Name"); NameValuePair found = null; for (final NameValuePair current : this.parameters) { if (current.getName().equalsIgnoreCase(name)) { found = current; break; } } return found; } @Override public String toString() { final StringBuilder buffer = new StringBuilder(); buffer.append(this.name); if (this.value != null) { buffer.append("="); buffer.append(this.value); } for (final NameValuePair parameter : this.parameters) { buffer.append("; "); buffer.append(parameter); } return buffer.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderElementIterator.java0100644 0000000 0000000 00000004540 00000250600 030202 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.util.Iterator; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HeaderElement; import org.apache.hc.core5.util.Args; /** * {@link Iterator} of {@link org.apache.hc.core5.http.HeaderElement}s. * * @since 4.0 */ public class BasicHeaderElementIterator extends AbstractHeaderElementIterator { private final HeaderValueParser parser; /** * Creates a new instance of BasicHeaderElementIterator */ public BasicHeaderElementIterator( final Iterator headerIterator, final HeaderValueParser parser) { super(headerIterator); this.parser = Args.notNull(parser, "Parser"); } public BasicHeaderElementIterator(final Iterator headerIterator) { this(headerIterator, BasicHeaderValueParser.INSTANCE); } @Override HeaderElement parseHeaderElement(final CharSequence buf, final ParserCursor cursor) { final HeaderElement e = this.parser.parseHeaderElement(buf, cursor); if (!(e.getName().isEmpty() && e.getValue() == null)) { return e; } return null; } } httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderIterator.java0100644 0000000 0000000 00000011116 00000250600 026665 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.util.Iterator; import java.util.NoSuchElementException; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.util.Args; /** * {@link Iterator} of {@link org.apache.hc.core5.http.Header}s. * * @since 4.0 */ public class BasicHeaderIterator implements Iterator
{ /** * An array of headers to iterate over. * Not all elements of this array are necessarily part of the iteration. * This array will never be modified by the iterator. * Derived implementations are expected to adhere to this restriction. */ private final Header[] allHeaders; /** * The position of the next header in {@link #allHeaders allHeaders}. * Negative if the iteration is over. */ private int currentIndex; /** * The header name to filter by. * {@code null} to iterate over all headers in the array. */ private final String headerName; /** * Creates a new header iterator. * * @param headers an array of headers over which to iterate * @param name the name of the headers over which to iterate, or * {@code null} for any */ public BasicHeaderIterator(final Header[] headers, final String name) { super(); this.allHeaders = Args.notNull(headers, "Header array"); this.headerName = name; this.currentIndex = findNext(-1); } /** * Determines the index of the next header. * * @param pos one less than the index to consider first, * -1 to search for the first header * * @return the index of the next header that matches the filter name, * or negative if there are no more headers */ private int findNext(final int pos) { int from = pos; if (from < -1) { return -1; } final int to = this.allHeaders.length - 1; boolean found = false; while (!found && from < to) { from++; found = filterHeader(from); } return found ? from : -1; } /** * Checks whether a header is part of the iteration. * * @param index the index of the header to check * * @return {@code true} if the header should be part of the * iteration, {@code false} to skip */ private boolean filterHeader(final int index) { return this.headerName == null || this.headerName.equalsIgnoreCase(this.allHeaders[index].getName()); } @Override public boolean hasNext() { return this.currentIndex >= 0; } /** * Obtains the next header from this iteration. * * @return the next header in this iteration * * @throws NoSuchElementException if there are no more headers */ @Override public Header next() throws NoSuchElementException { final int current = this.currentIndex; if (current < 0) { throw new NoSuchElementException("Iteration already finished."); } this.currentIndex = findNext(current); return this.allHeaders[current]; } /** * Removing headers is not supported. * * @throws UnsupportedOperationException always */ @Override public void remove() throws UnsupportedOperationException { throw new UnsupportedOperationException("Removing headers is not supported."); } } httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderValueFormatter.java0100644 0000000 0000000 00000011503 00000250600 030034 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HeaderElement; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; /** * Default {@link org.apache.hc.core5.http.message.HeaderValueFormatter} implementation. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class BasicHeaderValueFormatter implements HeaderValueFormatter { public final static BasicHeaderValueFormatter INSTANCE = new BasicHeaderValueFormatter(); private final static String SEPARATORS = " ;,:@()<>\\\"/[]?={}\t"; private final static String UNSAFE_CHARS = "\"\\"; public BasicHeaderValueFormatter() { super(); } @Override public void formatElements( final CharArrayBuffer buffer, final HeaderElement[] elems, final boolean quote) { Args.notNull(buffer, "Char array buffer"); Args.notNull(elems, "Header element array"); for (int i = 0; i < elems.length; i++) { if (i > 0) { buffer.append(", "); } formatHeaderElement(buffer, elems[i], quote); } } @Override public void formatHeaderElement( final CharArrayBuffer buffer, final HeaderElement elem, final boolean quote) { Args.notNull(buffer, "Char array buffer"); Args.notNull(elem, "Header element"); buffer.append(elem.getName()); final String value = elem.getValue(); if (value != null) { buffer.append('='); formatValue(buffer, value, quote); } final int c = elem.getParameterCount(); if (c > 0) { for (int i = 0; i < c; i++) { buffer.append("; "); formatNameValuePair(buffer, elem.getParameter(i), quote); } } } @Override public void formatParameters( final CharArrayBuffer buffer, final NameValuePair[] nvps, final boolean quote) { Args.notNull(buffer, "Char array buffer"); Args.notNull(nvps, "Header parameter array"); for (int i = 0; i < nvps.length; i++) { if (i > 0) { buffer.append("; "); } formatNameValuePair(buffer, nvps[i], quote); } } @Override public void formatNameValuePair( final CharArrayBuffer buffer, final NameValuePair nvp, final boolean quote) { Args.notNull(buffer, "Char array buffer"); Args.notNull(nvp, "Name / value pair"); buffer.append(nvp.getName()); final String value = nvp.getValue(); if (value != null) { buffer.append('='); formatValue(buffer, value, quote); } } void formatValue(final CharArrayBuffer buffer, final String value, final boolean quote) { boolean quoteFlag = quote; if (!quoteFlag) { for (int i = 0; i < value.length() && !quoteFlag; i++) { quoteFlag = isSeparator(value.charAt(i)); } } if (quoteFlag) { buffer.append('"'); } for (int i = 0; i < value.length(); i++) { final char ch = value.charAt(i); if (isUnsafe(ch)) { buffer.append('\\'); } buffer.append(ch); } if (quoteFlag) { buffer.append('"'); } } boolean isSeparator(final char ch) { return SEPARATORS.indexOf(ch) >= 0; } boolean isUnsafe(final char ch) { return UNSAFE_CHARS.indexOf(ch) >= 0; } } httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderValueParser.java0100644 0000000 0000000 00000012707 00000250600 027334 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.util.ArrayList; import java.util.List; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HeaderElement; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Tokenizer; /** * Default {@link org.apache.hc.core5.http.message.HeaderValueParser} implementation. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class BasicHeaderValueParser implements HeaderValueParser { public final static BasicHeaderValueParser INSTANCE = new BasicHeaderValueParser(); private final static char PARAM_DELIMITER = ';'; private final static char ELEM_DELIMITER = ','; private static final Tokenizer.Delimiter TOKEN_DELIMITER = Tokenizer.delimiters('=', PARAM_DELIMITER, ELEM_DELIMITER); private static final Tokenizer.Delimiter VALUE_DELIMITER = Tokenizer.delimiters(PARAM_DELIMITER, ELEM_DELIMITER); private final Tokenizer tokenizer; public BasicHeaderValueParser() { this.tokenizer = Tokenizer.INSTANCE; } /** * An empty immutable {@code HeaderElement} array. */ private static final HeaderElement[] EMPTY_HEADER_ELEMENT_ARRAY = {}; /** * An empty immutable {@code NameValuePair} array. */ private static final NameValuePair[] EMPTY_NAME_VALUE_ARRAY = {}; @Override public HeaderElement[] parseElements(final CharSequence buffer, final ParserCursor cursor) { Args.notNull(buffer, "Char sequence"); Args.notNull(cursor, "Parser cursor"); final List elements = new ArrayList<>(); while (!cursor.atEnd()) { final HeaderElement element = parseHeaderElement(buffer, cursor); if (!(element.getName().isEmpty() && element.getValue() == null)) { elements.add(element); } } return elements.toArray(EMPTY_HEADER_ELEMENT_ARRAY); } @Override public HeaderElement parseHeaderElement(final CharSequence buffer, final ParserCursor cursor) { Args.notNull(buffer, "Char sequence"); Args.notNull(cursor, "Parser cursor"); final NameValuePair nvp = parseNameValuePair(buffer, cursor); NameValuePair[] params = null; if (!cursor.atEnd()) { final char ch = buffer.charAt(cursor.getPos()); if (ch == PARAM_DELIMITER || ch == ELEM_DELIMITER) { cursor.updatePos(cursor.getPos() + 1); } if (ch != ELEM_DELIMITER) { params = parseParameters(buffer, cursor); } } return new BasicHeaderElement(nvp.getName(), nvp.getValue(), params); } @Override public NameValuePair[] parseParameters(final CharSequence buffer, final ParserCursor cursor) { Args.notNull(buffer, "Char sequence"); Args.notNull(cursor, "Parser cursor"); tokenizer.skipWhiteSpace(buffer, cursor); final List params = new ArrayList<>(); while (!cursor.atEnd()) { final NameValuePair param = parseNameValuePair(buffer, cursor); params.add(param); if (!cursor.atEnd()) { final char ch = buffer.charAt(cursor.getPos()); if (ch == PARAM_DELIMITER) { cursor.updatePos(cursor.getPos() + 1); } if (ch == ELEM_DELIMITER) { break; } } } return params.toArray(EMPTY_NAME_VALUE_ARRAY); } @Override public NameValuePair parseNameValuePair(final CharSequence buffer, final ParserCursor cursor) { Args.notNull(buffer, "Char sequence"); Args.notNull(cursor, "Parser cursor"); final String name = tokenizer.parseToken(buffer, cursor, TOKEN_DELIMITER); if (cursor.atEnd()) { return new BasicNameValuePair(name, null); } final char delim = buffer.charAt(cursor.getPos()); if (delim != '=') { return new BasicNameValuePair(name, null); } cursor.updatePos(cursor.getPos() + 1); final String value = tokenizer.parseValue(buffer, cursor, VALUE_DELIMITER); return new BasicNameValuePair(name, value); } } httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHttpRequest.java0100644 0000000 0000000 00000023241 00000250600 026255 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.net.URI; import java.net.URISyntaxException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TextUtils; /** * Basic implementation of {@link HttpRequest}. * * @since 4.0 */ public class BasicHttpRequest extends HeaderGroup implements HttpRequest { private static final long serialVersionUID = 1L; private final String method; private String path; private String scheme; private URIAuthority authority; private ProtocolVersion version; private URI requestUri; private boolean absoluteRequestUri; /** * Creates request message with the given method, host and request path. * * @param method request method. * @param scheme request scheme. * @param authority request authority. * @param path request path. * * @since 5.1 */ public BasicHttpRequest(final String method, final String scheme, final URIAuthority authority, final String path) { super(); this.method = Args.notNull(method, "Method name"); this.scheme = scheme; this.authority = authority; this.path = path; } /** * Creates request message with the given method and request path. * * @param method request method. * @param path request path. */ public BasicHttpRequest(final String method, final String path) { super(); this.method = method; if (path != null) { try { setUri(new URI(path)); } catch (final URISyntaxException ex) { this.path = path; } } } /** * Creates request message with the given method, host and request path. * * @param method request method. * @param host request host. * @param path request path. * * @since 5.0 */ public BasicHttpRequest(final String method, final HttpHost host, final String path) { super(); this.method = Args.notNull(method, "Method name"); this.scheme = host != null ? host.getSchemeName() : null; this.authority = host != null ? new URIAuthority(host) : null; this.path = path; } /** * Creates request message with the given method, request URI. * * @param method request method. * @param requestUri request URI. * * @since 5.0 */ public BasicHttpRequest(final String method, final URI requestUri) { super(); this.method = Args.notNull(method, "Method name"); setUri(Args.notNull(requestUri, "Request URI")); } /** * Creates request message with the given method and request path. * * @param method request method. * @param path request path. * * @since 5.0 */ public BasicHttpRequest(final Method method, final String path) { super(); this.method = Args.notNull(method, "Method").name(); if (path != null) { try { setUri(new URI(path)); } catch (final URISyntaxException ex) { this.path = path; } } } /** * Creates request message with the given method, host and request path. * * @param method request method. * @param host request host. * @param path request path. * * @since 5.0 */ public BasicHttpRequest(final Method method, final HttpHost host, final String path) { super(); this.method = Args.notNull(method, "Method").name(); this.scheme = host != null ? host.getSchemeName() : null; this.authority = host != null ? new URIAuthority(host) : null; this.path = path; } /** * Creates request message with the given method, request URI. * * @param method request method. * @param requestUri request URI. * * @since 5.0 */ public BasicHttpRequest(final Method method, final URI requestUri) { super(); this.method = Args.notNull(method, "Method").name(); setUri(Args.notNull(requestUri, "Request URI")); } @Override public void addHeader(final String name, final Object value) { Args.notNull(name, "Header name"); addHeader(new BasicHeader(name, value)); } @Override public void setHeader(final String name, final Object value) { Args.notNull(name, "Header name"); setHeader(new BasicHeader(name, value)); } @Override public void setVersion(final ProtocolVersion version) { this.version = version; } @Override public ProtocolVersion getVersion() { return this.version; } @Override public String getMethod() { return this.method; } @Override public String getPath() { return this.path; } @Override public void setPath(final String path) { this.path = path; this.requestUri = null; } @Override public String getScheme() { return this.scheme; } @Override public void setScheme(final String scheme) { this.scheme = scheme; this.requestUri = null; } @Override public URIAuthority getAuthority() { return this.authority; } @Override public void setAuthority(final URIAuthority authority) { this.authority = authority; this.requestUri = null; } /** * Sets whether {@link #getRequestUri()} should return the request URI in an absolute form. *

* This flag can used when the request is going to be transmitted via an HTTP/1.1 proxy. *

* * @param absoluteRequestUri Whether {@link #getRequestUri()} should return the request URI in an absolute form. * @since 5.1 */ public void setAbsoluteRequestUri(final boolean absoluteRequestUri) { this.absoluteRequestUri = absoluteRequestUri; } @Override public String getRequestUri() { if (absoluteRequestUri) { final StringBuilder buf = new StringBuilder(); assembleRequestUri(buf); return buf.toString(); } return getPath(); } @Override public void setUri(final URI requestUri) { this.scheme = requestUri.getScheme(); if (requestUri.getHost() != null) { this.authority = new URIAuthority( requestUri.getRawUserInfo(), requestUri.getHost(), requestUri.getPort()); } else if (requestUri.getRawAuthority() != null) { try { this.authority = URIAuthority.create(requestUri.getRawAuthority()); } catch (final URISyntaxException ignore) { this.authority = null; } } else { this.authority = null; } final StringBuilder buf = new StringBuilder(); final String rawPath = requestUri.getRawPath(); if (!TextUtils.isBlank(rawPath)) { buf.append(rawPath); } else { buf.append("/"); } final String query = requestUri.getRawQuery(); if (query != null) { buf.append('?').append(query); } this.path = buf.toString(); this.requestUri = null; } private void assembleRequestUri(final StringBuilder buf) { if (this.authority != null) { buf.append(this.scheme != null ? this.scheme : URIScheme.HTTP.id).append("://"); buf.append(this.authority.getHostName()); if (this.authority.getPort() >= 0) { buf.append(":").append(this.authority.getPort()); } } if (this.path == null) { buf.append("/"); } else { if (buf.length() > 0 && !this.path.startsWith("/")) { buf.append("/"); } buf.append(this.path); } } @Override public URI getUri() throws URISyntaxException { if (this.requestUri == null) { final StringBuilder buf = new StringBuilder(); assembleRequestUri(buf); this.requestUri = new URI(buf.toString()); } return this.requestUri; } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append(method).append(" "); assembleRequestUri(buf); return buf.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHttpResponse.java0100644 0000000 0000000 00000012636 00000250600 026431 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.util.Locale; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.ReasonPhraseCatalog; import org.apache.hc.core5.http.impl.EnglishReasonPhraseCatalog; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TextUtils; /** * Basic implementation of {@link HttpResponse}. * * @since 4.0 */ public class BasicHttpResponse extends HeaderGroup implements HttpResponse { private static final long serialVersionUID = 1L; private final ReasonPhraseCatalog reasonCatalog; private ProtocolVersion version; private Locale locale; private int code; private String reasonPhrase; /** * Creates a new response. * * @param code the status code * @param catalog the reason phrase catalog, or * {@code null} to disable automatic * reason phrase lookup * @param locale the locale for looking up reason phrases, or * {@code null} for the system locale */ public BasicHttpResponse( final int code, final ReasonPhraseCatalog catalog, final Locale locale) { super(); this.code = Args.positive(code, "Status code"); this.reasonCatalog = catalog != null ? catalog : EnglishReasonPhraseCatalog.INSTANCE; this.locale = locale; } /** * Creates a new response. * * @param code the status code of the response * @param reasonPhrase the reason phrase to the status code, or {@code null} */ public BasicHttpResponse(final int code, final String reasonPhrase) { this.code = Args.positive(code, "Status code"); this.reasonPhrase = reasonPhrase; this.reasonCatalog = EnglishReasonPhraseCatalog.INSTANCE; } /** * Creates a new response. * * @param code the status code of the response */ public BasicHttpResponse(final int code) { this.code = Args.positive(code, "Status code"); this.reasonPhrase = null; this.reasonCatalog = EnglishReasonPhraseCatalog.INSTANCE; } @Override public void addHeader(final String name, final Object value) { Args.notNull(name, "Header name"); addHeader(new BasicHeader(name, value)); } @Override public void setHeader(final String name, final Object value) { Args.notNull(name, "Header name"); setHeader(new BasicHeader(name, value)); } @Override public void setVersion(final ProtocolVersion version) { this.version = version; } @Override public ProtocolVersion getVersion() { return this.version; } @Override public int getCode() { return this.code; } @Override public Locale getLocale() { return this.locale; } @Override public void setCode(final int code) { Args.positive(code, "Status code"); this.code = code; this.reasonPhrase = null; } @Override public String getReasonPhrase() { return this.reasonPhrase != null ? this.reasonPhrase : getReason(this.code); } @Override public void setReasonPhrase(final String reason) { this.reasonPhrase = TextUtils.isBlank(reason) ? null : reason; } @Override public void setLocale(final Locale locale) { this.locale = Args.notNull(locale, "Locale"); } /** * Looks up a reason phrase. * This method evaluates the currently set catalog and locale. * It also handles a missing catalog. * * @param code the status code for which to look up the reason * * @return the reason phrase, or {@code null} if there is none */ protected String getReason(final int code) { return this.reasonCatalog != null ? this.reasonCatalog.getReason(code, this.locale != null ? this.locale : Locale.getDefault()) : null; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append(this.code).append(' ').append(this.reasonPhrase).append(' ').append(this.version); return sb.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicLineFormatter.java0100644 0000000 0000000 00000007472 00000250600 026550 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; /** * Default {@link org.apache.hc.core5.http.message.LineFormatter} implementation. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class BasicLineFormatter implements LineFormatter { public final static BasicLineFormatter INSTANCE = new BasicLineFormatter(); public BasicLineFormatter() { super(); } void formatProtocolVersion(final CharArrayBuffer buffer, final ProtocolVersion version) { buffer.append(version.format()); } @Override public void formatRequestLine(final CharArrayBuffer buffer, final RequestLine reqline) { Args.notNull(buffer, "Char array buffer"); Args.notNull(reqline, "Request line"); buffer.append(reqline.getMethod()); buffer.append(' '); buffer.append(reqline.getUri()); buffer.append(' '); formatProtocolVersion(buffer, reqline.getProtocolVersion()); } @Override public void formatStatusLine(final CharArrayBuffer buffer, final StatusLine statusLine) { Args.notNull(buffer, "Char array buffer"); Args.notNull(statusLine, "Status line"); formatProtocolVersion(buffer, statusLine.getProtocolVersion()); buffer.append(' '); buffer.append(Integer.toString(statusLine.getStatusCode())); buffer.append(' '); // keep whitespace even if reason phrase is empty final String reasonPhrase = statusLine.getReasonPhrase(); if (reasonPhrase != null) { buffer.append(reasonPhrase); } } @Override public void formatHeader(final CharArrayBuffer buffer, final Header header) { Args.notNull(buffer, "Char array buffer"); Args.notNull(header, "Header"); buffer.append(header.getName()); buffer.append(": "); final String value = header.getValue(); if (value != null) { buffer.ensureCapacity(buffer.length() + value.length()); for (int valueIndex = 0; valueIndex < value.length(); valueIndex++) { char valueChar = value.charAt(valueIndex); if (valueChar == '\r' || valueChar == '\n' || valueChar == '\f' || valueChar == 0x0b) { valueChar = ' '; } buffer.append(valueChar); } } } } httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicLineParser.java0100644 0000000 0000000 00000020500 00000250600 026024 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.ProtocolVersionParser; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.util.Tokenizer; /** * Default {@link org.apache.hc.core5.http.message.LineParser} implementation. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class BasicLineParser implements LineParser { public final static BasicLineParser INSTANCE = new BasicLineParser(); private static final Tokenizer.Delimiter BLANKS = Tokenizer.delimiters(' ', '\t'); private static final Tokenizer.Delimiter COLON = Tokenizer.delimiters(':'); /** * A version of the protocol to parse. * The version is typically not relevant, but the protocol name. */ private final ProtocolVersion protocol; private final Tokenizer tokenizer; /** * Creates a new line parser for the given HTTP-like protocol. * * @param proto a version of the protocol to parse, or * {@code null} for HTTP. The actual version * is not relevant, only the protocol name. */ public BasicLineParser(final ProtocolVersion proto) { this.protocol = proto != null ? proto : HttpVersion.HTTP_1_1; this.tokenizer = Tokenizer.INSTANCE; } /** * Creates a new line parser for HTTP. */ public BasicLineParser() { this(null); } ProtocolVersion parseProtocolVersion( final CharArrayBuffer buffer, final ParserCursor cursor) throws ParseException { final String protoname = this.protocol.getProtocol(); final int protolength = protoname.length(); this.tokenizer.skipWhiteSpace(buffer, cursor); final int pos = cursor.getPos(); // long enough for "HTTP/1.1"? if (pos + protolength + 4 > cursor.getUpperBound()) { throw new ParseException("Invalid protocol version", buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos()); } // check the protocol name and slash boolean ok = true; for (int i = 0; ok && i < protolength; i++) { ok = buffer.charAt(pos + i) == protoname.charAt(i); } if (ok) { ok = buffer.charAt(pos + protolength) == '/'; } if (!ok) { throw new ParseException("Invalid protocol version", buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos()); } cursor.updatePos(pos + protolength + 1); return ProtocolVersionParser.INSTANCE.parse(protoname, HttpVersion::get, buffer, cursor, null); } /** * Parses a request line. * * @param buffer a buffer holding the line to parse * * @return the parsed request line * * @throws ParseException in case of a parse error */ @Override public RequestLine parseRequestLine(final CharArrayBuffer buffer) throws ParseException { Args.notNull(buffer, "Char array buffer"); final ParserCursor cursor = new ParserCursor(0, buffer.length()); this.tokenizer.skipWhiteSpace(buffer, cursor); final String method = this.tokenizer.parseToken(buffer, cursor, BLANKS); if (TextUtils.isEmpty(method)) { throw new ParseException("Invalid request line", buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos()); } this.tokenizer.skipWhiteSpace(buffer, cursor); final String uri = this.tokenizer.parseToken(buffer, cursor, BLANKS); if (TextUtils.isEmpty(uri)) { throw new ParseException("Invalid request line", buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos()); } final ProtocolVersion ver = parseProtocolVersion(buffer, cursor); this.tokenizer.skipWhiteSpace(buffer, cursor); if (!cursor.atEnd()) { throw new ParseException("Invalid request line", buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos()); } return new RequestLine(method, uri, ver); } @Override public StatusLine parseStatusLine(final CharArrayBuffer buffer) throws ParseException { Args.notNull(buffer, "Char array buffer"); final ParserCursor cursor = new ParserCursor(0, buffer.length()); this.tokenizer.skipWhiteSpace(buffer, cursor); final ProtocolVersion ver = parseProtocolVersion(buffer, cursor); this.tokenizer.skipWhiteSpace(buffer, cursor); int statusCode = 0; for (int i = 1; i <= 3; i++) { if (cursor.atEnd()) { throw new ParseException("Status line contains invalid status code", buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos()); } final char ch = buffer.charAt(cursor.getPos()); if (ch >= '0' && ch <= '9') { statusCode = (statusCode * 10) + (ch - '0'); } else { throw new ParseException("Status line contains invalid status code", buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos()); } cursor.updatePos(cursor.getPos() + 1); } if (!cursor.atEnd()) { final char ch = buffer.charAt(cursor.getPos()); if (Tokenizer.isWhitespace(ch)) { cursor.updatePos(cursor.getPos() + 1); } else { throw new ParseException("Status line contains invalid status code", buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos()); } } this.tokenizer.skipWhiteSpace(buffer, cursor); final String text = buffer.substringTrimmed(cursor.getPos(), cursor.getUpperBound()); return new StatusLine(ver, statusCode, text); } @Override public Header parseHeader(final CharArrayBuffer buffer) throws ParseException { Args.notNull(buffer, "Char array buffer"); final ParserCursor cursor = new ParserCursor(0, buffer.length()); this.tokenizer.skipWhiteSpace(buffer, cursor); final String name = this.tokenizer.parseToken(buffer, cursor, COLON); if (cursor.getPos() == cursor.getLowerBound() || cursor.getPos() == cursor.getUpperBound() || buffer.charAt(cursor.getPos()) != ':' || TextUtils.isEmpty(name) || Tokenizer.isWhitespace(buffer.charAt(cursor.getPos() - 1))) { throw new ParseException("Invalid header", buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos()); } final String value = buffer.substringTrimmed(cursor.getPos() + 1, cursor.getUpperBound()); return new BasicHeader(name, value); } } httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicListHeaderIterator.java0100644 0000000 0000000 00000011754 00000250600 027531 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; /** * {@link Iterator} of {@link org.apache.hc.core5.http.Header}s. For use by {@link HeaderGroup}. * * @since 4.0 */ class BasicListHeaderIterator implements Iterator
{ /** * A list of headers to iterate over. * Not all elements of this array are necessarily part of the iteration. */ private final List allHeaders; /** * The position of the next header in {@link #allHeaders allHeaders}. * Negative if the iteration is over. */ private int currentIndex; /** * The position of the last returned header. * Negative if none has been returned so far. */ private int lastIndex; /** * The header name to filter by. * {@code null} to iterate over all headers in the array. */ private final String headerName; /** * Creates a new header iterator. * * @param headers a list of headers over which to iterate * @param name the name of the headers over which to iterate, or * {@code null} for any */ public BasicListHeaderIterator(final List headers, final String name) { super(); this.allHeaders = Args.notNull(headers, "Header list"); this.headerName = name; this.currentIndex = findNext(-1); this.lastIndex = -1; } /** * Determines the index of the next header. * * @param pos one less than the index to consider first, * -1 to search for the first header * * @return the index of the next header that matches the filter name, * or negative if there are no more headers */ protected int findNext(final int pos) { int from = pos; if (from < -1) { return -1; } final int to = this.allHeaders.size() - 1; boolean found = false; while (!found && from < to) { from++; found = filterHeader(from); } return found ? from : -1; } /** * Checks whether a header is part of the iteration. * * @param index the index of the header to check * * @return {@code true} if the header should be part of the * iteration, {@code false} to skip */ private boolean filterHeader(final int index) { if (this.headerName == null) { return true; } // non-header elements, including null, will trigger exceptions final String name = (this.allHeaders.get(index)).getName(); return this.headerName.equalsIgnoreCase(name); } @Override public boolean hasNext() { return this.currentIndex >= 0; } /** * Obtains the next header from this iteration. * * @return the next header in this iteration * * @throws NoSuchElementException if there are no more headers */ @Override public Header next() throws NoSuchElementException { final int current = this.currentIndex; if (current < 0) { throw new NoSuchElementException("Iteration already finished."); } this.lastIndex = current; this.currentIndex = findNext(current); return this.allHeaders.get(current); } /** * Removes the header that was returned last. */ @Override public void remove() throws UnsupportedOperationException { Asserts.check(this.lastIndex >= 0, "No header to remove"); this.allHeaders.remove(this.lastIndex); this.lastIndex = -1; this.currentIndex--; // adjust for the removed element } } httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicNameValuePair.java0100644 0000000 0000000 00000006604 00000250600 026462 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.io.Serializable; import java.util.Objects; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.LangUtils; import org.apache.hc.core5.util.TextUtils; /** * Basic implementation of {@link NameValuePair}. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class BasicNameValuePair implements NameValuePair, Serializable { private static final long serialVersionUID = -6437800749411518984L; private final String name; private final String value; /** * Default Constructor taking a name and a value. The value may be null. * * @param name The name. * @param value The value. */ public BasicNameValuePair(final String name, final String value) { super(); this.name = Args.notNull(name, "Name"); this.value = value; } @Override public String getName() { return this.name; } @Override public String getValue() { return this.value; } @Override public String toString() { // don't call complex default formatting for a simple toString if (this.value == null) { return name; } final int len = this.name.length() + 1 + this.value.length(); final StringBuilder buffer = new StringBuilder(len); buffer.append(this.name); buffer.append("="); buffer.append(this.value); return buffer.toString(); } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj instanceof BasicNameValuePair) { final BasicNameValuePair that = (BasicNameValuePair) obj; return this.name.equalsIgnoreCase(that.name) && Objects.equals(this.value, that.value); } return false; } @Override public int hashCode() { int hash = LangUtils.HASH_SEED; hash = LangUtils.hashCode(hash, TextUtils.toLowerCase(this.name)); hash = LangUtils.hashCode(hash, this.value); return hash; } } httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicTokenIterator.java0100644 0000000 0000000 00000004574 00000250600 026567 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.util.Iterator; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.util.Tokenizer; /** * {@link Iterator} of {@link org.apache.hc.core5.http.Header} tokens.. * * @since 4.0 */ public class BasicTokenIterator extends AbstractHeaderElementIterator { private static final Tokenizer.Delimiter COMMA = Tokenizer.delimiters(','); private final Tokenizer tokenizer; /** * Creates a new instance of {@link BasicTokenIterator}. * * @param headerIterator the iterator for the headers to tokenize */ public BasicTokenIterator(final Iterator headerIterator) { super(headerIterator); this.tokenizer = Tokenizer.INSTANCE; } @Override String parseHeaderElement(final CharSequence buf, final ParserCursor cursor) { final String token = this.tokenizer.parseToken(buf, cursor, COMMA); if (!cursor.atEnd()) { final int pos = cursor.getPos(); if (buf.charAt(pos) == ',') { cursor.updatePos(pos + 1); } } return !TextUtils.isBlank(token) ? token : null; } } httpcore5/src/main/java/org/apache/hc/core5/http/message/BufferedHeader.java0100644 0000000 0000000 00000011535 00000250600 025661 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.io.Serializable; import org.apache.hc.core5.http.Chars; import org.apache.hc.core5.http.FormattedHeader; import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; import org.apache.hc.core5.util.Tokenizer; /** * This class represents a raw HTTP header whose content is parsed 'on demand' * only when the header value needs to be consumed. * * @since 4.0 */ public class BufferedHeader implements FormattedHeader, Serializable { private static final long serialVersionUID = -2768352615787625448L; /** * Header name. */ private final String name; /** * The buffer containing the entire header line. */ private final CharArrayBuffer buffer; /** * The beginning of the header value in the buffer */ private final int valuePos; private String value; /** * @since 5.0 */ public static BufferedHeader create(final CharArrayBuffer buffer) { try { return new BufferedHeader(buffer); } catch (final ParseException ex) { throw new IllegalArgumentException(ex.getMessage()); } } /** * Creates a new header from a buffer. * The name of the header will be parsed immediately, * the value only if it is accessed. * * @param buffer the buffer containing the header to represent * * @throws ParseException in case of a parse error */ public BufferedHeader(final CharArrayBuffer buffer) throws ParseException { this(buffer, true); } BufferedHeader(final CharArrayBuffer buffer, final boolean strict) throws ParseException { super(); Args.notNull(buffer, "Char array buffer"); final int colon = buffer.indexOf(':'); if (colon <= 0) { throw new ParseException("Invalid header", buffer, 0, buffer.length()); } if (strict && Tokenizer.isWhitespace(buffer.charAt(colon - 1))) { throw new ParseException("Invalid header", buffer, 0, buffer.length(), colon - 1); } final String s = buffer.substringTrimmed(0, colon); if (s.isEmpty()) { throw new ParseException("Invalid header", buffer, 0, buffer.length(), colon); } this.buffer = buffer; this.name = s; this.valuePos = colon + 1; } @Override public String getName() { return this.name; } @Override public String getValue() { if (value == null) { int beginIdx = valuePos; int endIdx = buffer.length(); while (beginIdx < buffer.length() && Tokenizer.isWhitespace(buffer.charAt(beginIdx))) { beginIdx++; } while (endIdx > beginIdx && Tokenizer.isWhitespace(buffer.charAt(endIdx - 1))) { endIdx--; } final StringBuilder buf = new StringBuilder(endIdx - beginIdx); for (int i = beginIdx; i < endIdx; i++) { final char ch = buffer.charAt(i); if (ch == Chars.CR || ch == Chars.LF || ch == Chars.NULL) { buf.append((char) Chars.SP); } else { buf.append(ch); } } value = buf.toString(); } return value; } @Override public boolean isSensitive() { return false; } @Override public int getValuePos() { return this.valuePos; } @Override public CharArrayBuffer getBuffer() { return this.buffer; } @Override public String toString() { return this.buffer.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/message/HeaderGroup.java0100644 0000000 0000000 00000030220 00000250600 025223 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Objects; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.MessageHeaders; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.util.CharArrayBuffer; import org.apache.hc.core5.util.TextUtils; /** * A class for combining a set of headers. This class allows for multiple headers with the same name * and keeps track of the order in which headers were added. * * @since 4.0 */ public class HeaderGroup implements MessageHeaders, Serializable { private static final long serialVersionUID = 2608834160639271617L; private static final Header[] EMPTY = new Header[] {}; /** The list of headers for this group, in the order in which they were added */ private final List
headers; /** * Constructor for HeaderGroup. */ public HeaderGroup() { this.headers = new ArrayList<>(16); } /** * Removes all headers. */ public void clear() { headers.clear(); } /** * Adds the given header to the group. The order in which this header was * added is preserved. * * @param header the header to add */ public void addHeader(final Header header) { if (header == null) { return; } headers.add(header); } /** * Removes the first given header. * * @param header the header to remove * @return {@code true} if a header was removed as a result of this call. */ public boolean removeHeader(final Header header) { if (header == null) { return false; } for (int i = 0; i < this.headers.size(); i++) { final Header current = this.headers.get(i); if (headerEquals(header, current)) { this.headers.remove(current); return true; } } return false; } private boolean headerEquals(final Header header1, final Header header2) { return header2 == header1 || header2.getName().equalsIgnoreCase(header1.getName()) && Objects.equals(header1.getValue(), header2.getValue()); } /** * Removes all headers that match the given header. * * @param header the header to remove * @return {@code true} if any header was removed as a result of this call. * * @since 5.0 */ public boolean removeHeaders(final Header header) { if (header == null) { return false; } boolean removed = false; for (final Iterator
iterator = headerIterator(); iterator.hasNext();) { final Header current = iterator.next(); if (headerEquals(header, current)) { iterator.remove(); removed = true; } } return removed; } /** * Replaces the first occurrence of the header with the same name. If no header with * the same name is found the given header is added to the end of the list. * * @param header the new header that should replace the first header with the same * name if present in the list. * * @since 5.0 */ public void setHeader(final Header header) { if (header == null) { return; } for (int i = 0; i < this.headers.size(); i++) { final Header current = this.headers.get(i); if (current.getName().equalsIgnoreCase(header.getName())) { this.headers.set(i, header); return; } } this.headers.add(header); } /** * Sets all of the headers contained within this group overriding any * existing headers. The headers are added in the order in which they appear * in the array. * * @param headers the headers to set */ public void setHeaders(final Header... headers) { clear(); if (headers == null) { return; } Collections.addAll(this.headers, headers); } /** * Gets a header representing all of the header values with the given name. * If more that one header with the given name exists the values will be * combined with a ",". * *

Header name comparison is case insensitive. * * @param name the name of the header(s) to get * @return a header with a condensed value or {@code null} if no * headers by the given name are present */ public Header getCondensedHeader(final String name) { final Header[] hdrs = getHeaders(name); if (hdrs.length == 0) { return null; } else if (hdrs.length == 1) { return hdrs[0]; } else { final CharArrayBuffer valueBuffer = new CharArrayBuffer(128); valueBuffer.append(hdrs[0].getValue()); for (int i = 1; i < hdrs.length; i++) { valueBuffer.append(", "); valueBuffer.append(hdrs[i].getValue()); } return new BasicHeader(TextUtils.toLowerCase(name), valueBuffer.toString()); } } /** * Gets all of the headers with the given name. The returned array * maintains the relative order in which the headers were added. * *

Header name comparison is case insensitive. * * @param name the name of the header(s) to get * * @return an array of length ≥ 0 */ @Override public Header[] getHeaders(final String name) { List

headersFound = null; for (int i = 0; i < this.headers.size(); i++) { final Header header = this.headers.get(i); if (header.getName().equalsIgnoreCase(name)) { if (headersFound == null) { headersFound = new ArrayList<>(); } headersFound.add(header); } } return headersFound != null ? headersFound.toArray(EMPTY) : EMPTY; } /** * Gets the first header with the given name. * *

Header name comparison is case insensitive. * * @param name the name of the header to get * @return the first header or {@code null} */ @Override public Header getFirstHeader(final String name) { for (int i = 0; i < this.headers.size(); i++) { final Header header = this.headers.get(i); if (header.getName().equalsIgnoreCase(name)) { return header; } } return null; } /** * Gets single first header with the given name. * *

Header name comparison is case insensitive. * * @param name the name of the header to get * @return the first header or {@code null} * @throws ProtocolException in case multiple headers with the given name are found. */ @Override public Header getHeader(final String name) throws ProtocolException { int count = 0; Header singleHeader = null; for (int i = 0; i < this.headers.size(); i++) { final Header header = this.headers.get(i); if (header.getName().equalsIgnoreCase(name)) { singleHeader = header; count++; } } if (count > 1) { throw new ProtocolException("multiple '%s' headers found", name); } return singleHeader; } /** * Gets the last header with the given name. * *

Header name comparison is case insensitive. * * @param name the name of the header to get * @return the last header or {@code null} */ @Override public Header getLastHeader(final String name) { // start at the end of the list and work backwards for (int i = headers.size() - 1; i >= 0; i--) { final Header header = headers.get(i); if (header.getName().equalsIgnoreCase(name)) { return header; } } return null; } /** * Gets all of the headers contained within this group. * * @return an array of length ≥ 0 */ @Override public Header[] getHeaders() { return headers.toArray(EMPTY); } /** * Tests if headers with the given name are contained within this group. * *

Header name comparison is case insensitive. * * @param name the header name to test for * @return {@code true} if at least one header with the name is * contained, {@code false} otherwise */ @Override public boolean containsHeader(final String name) { for (int i = 0; i < this.headers.size(); i++) { final Header header = this.headers.get(i); if (header.getName().equalsIgnoreCase(name)) { return true; } } return false; } /** * Checks if a certain header is present in this message and how many times. *

Header name comparison is case insensitive. * * @param name the header name to check for. * @return number of occurrences of the header in the message. */ @Override public int countHeaders(final String name) { int count = 0; for (int i = 0; i < this.headers.size(); i++) { final Header header = this.headers.get(i); if (header.getName().equalsIgnoreCase(name)) { count++; } } return count; } /** * Returns an iterator over this group of headers. * * @return iterator over this group of headers. * * @since 5.0 */ @Override public Iterator

headerIterator() { return new BasicListHeaderIterator(this.headers, null); } /** * Returns an iterator over the headers with a given name in this group. * * @param name the name of the headers over which to iterate, or * {@code null} for all headers * * @return iterator over some headers in this group. * * @since 5.0 */ @Override public Iterator
headerIterator(final String name) { return new BasicListHeaderIterator(this.headers, name); } /** * Removes all headers with a given name in this group. * * @param name the name of the headers to be removed. * @return {@code true} if any header was removed as a result of this call. * * @since 5.0 */ public boolean removeHeaders(final String name) { if (name == null) { return false; } boolean removed = false; for (final Iterator
iterator = headerIterator(); iterator.hasNext(); ) { final Header header = iterator.next(); if (header.getName().equalsIgnoreCase(name)) { iterator.remove(); removed = true; } } return removed; } @Override public String toString() { return this.headers.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/message/HeaderValueFormatter.java0100644 0000000 0000000 00000006531 00000250600 027077 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.http.HeaderElement; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.util.CharArrayBuffer; /** * Interface for formatting elements of a header value. * Instances of this interface are expected to be stateless and thread-safe. * * @since 4.0 */ public interface HeaderValueFormatter { /** * Formats an array of header elements. * * @param buffer buffer to write formatted content to. * @param elems the header elements to format * @param quote {@code true} to always format with quoted values, * {@code false} to use quotes only when necessary */ void formatElements(CharArrayBuffer buffer, HeaderElement[] elems, boolean quote); /** * Formats one header element. * * @param buffer buffer to write formatted content to. * @param elem the header element to format * @param quote {@code true} to always format with quoted values, * {@code false} to use quotes only when necessary */ void formatHeaderElement(CharArrayBuffer buffer, HeaderElement elem, boolean quote); /** * Formats the parameters of a header element. * That's a list of name-value pairs, to be separated by semicolons. * This method will not generate a leading semicolon. * * @param buffer buffer to write formatted content to. * @param nvps the parameters (name-value pairs) to format * @param quote {@code true} to always format with quoted values, * {@code false} to use quotes only when necessary */ void formatParameters(CharArrayBuffer buffer, NameValuePair[] nvps, boolean quote); /** * Formats one name-value pair, where the value is optional. * * @param buffer buffer to write formatted content to. * @param nvp the name-value pair to format * @param quote {@code true} to always format with a quoted value, * {@code false} to use quotes only when necessary */ void formatNameValuePair(CharArrayBuffer buffer, NameValuePair nvp, boolean quote); } httpcore5/src/main/java/org/apache/hc/core5/http/message/HeaderValueParser.java0100644 0000000 0000000 00000007050 00000250600 026365 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.http.HeaderElement; import org.apache.hc.core5.http.NameValuePair; /** * Interface for parsing header values into elements. * Instances of this interface are expected to be stateless and thread-safe. * * @since 4.0 */ public interface HeaderValueParser { /** * Parses a header value into elements. * Parse errors are indicated as {@code RuntimeException}. * * @param buffer buffer holding the header value to parse * @param cursor the parser cursor containing the current position and * the bounds within the buffer for the parsing operation * * @return an array holding all elements of the header value */ HeaderElement[] parseElements(CharSequence buffer, ParserCursor cursor); /** * Parses a single header element. * A header element consist of a semicolon-separate list * of name=value definitions. * * @param buffer buffer holding the element to parse * @param cursor the parser cursor containing the current position and * the bounds within the buffer for the parsing operation * * @return the parsed element */ HeaderElement parseHeaderElement(CharSequence buffer, ParserCursor cursor); /** * Parses a list of name-value pairs. * These lists are used to specify parameters to a header element. * Parse errors are indicated as {@code ParseException}. * * @param buffer buffer holding the name-value list to parse * @param cursor the parser cursor containing the current position and * the bounds within the buffer for the parsing operation * * @return an array holding all items of the name-value list */ NameValuePair[] parseParameters(CharSequence buffer, ParserCursor cursor); /** * Parses a name=value specification, where the = and value are optional. * * @param buffer the buffer holding the name-value pair to parse * @param cursor the parser cursor containing the current position and * the bounds within the buffer for the parsing operation * * @return the name-value pair, where the value is {@code null} * if no value is specified */ NameValuePair parseNameValuePair(CharSequence buffer, ParserCursor cursor); } httpcore5/src/main/java/org/apache/hc/core5/http/message/HttpRequestWrapper.java0100644 0000000 0000000 00000005205 00000250600 026654 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.net.URI; import java.net.URISyntaxException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.net.URIAuthority; /** * Wraps an {@link HttpRequest}. * * {@link HttpRequest} wrapper. */ public class HttpRequestWrapper extends AbstractMessageWrapper implements HttpRequest { public HttpRequestWrapper(final HttpRequest message) { super(message); } @Override public String getMethod() { return getMessage().getMethod(); } @Override public String getPath() { return getMessage().getPath(); } @Override public void setPath(final String path) { getMessage().setPath(path); } @Override public String getScheme() { return getMessage().getScheme(); } @Override public void setScheme(final String scheme) { getMessage().setScheme(scheme); } @Override public URIAuthority getAuthority() { return getMessage().getAuthority(); } @Override public void setAuthority(final URIAuthority authority) { getMessage().setAuthority(authority); } @Override public String getRequestUri() { return getMessage().getRequestUri(); } @Override public URI getUri() throws URISyntaxException { return getMessage().getUri(); } @Override public void setUri(final URI requestUri) { getMessage().setUri(requestUri); } } httpcore5/src/main/java/org/apache/hc/core5/http/message/HttpResponseWrapper.java0100644 0000000 0000000 00000004216 00000250600 027023 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.util.Locale; import org.apache.hc.core5.http.HttpResponse; /** * Wraps an {@link HttpResponse}. * * {@link HttpResponse} wrapper. */ public class HttpResponseWrapper extends AbstractMessageWrapper implements HttpResponse { public HttpResponseWrapper(final HttpResponse message) { super(message); } @Override public int getCode() { return getMessage().getCode(); } @Override public void setCode(final int code) { getMessage().setCode(code); } @Override public String getReasonPhrase() { return getMessage().getReasonPhrase(); } @Override public void setReasonPhrase(final String reason) { getMessage().setReasonPhrase(reason); } @Override public Locale getLocale() { return getMessage().getLocale(); } @Override public void setLocale(final Locale loc) { getMessage().setLocale(loc); } } httpcore5/src/main/java/org/apache/hc/core5/http/message/LazyLaxLineParser.java0100644 0000000 0000000 00000004426 00000250600 026400 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; /** * Extension of {@link org.apache.hc.core5.http.message.BasicLineParser} that defers parsing of * header values. Header value is parsed only if accessed with * {@link org.apache.hc.core5.http.Header#getValue()}. *

* This parser unlike {@link BasicLineParser} and {@link LazyLineParser} * will not reject headers containing whitespaces between the header field * name and colon. *

* This parser should be used to parse response messages on the client server * or to parse both request and response messages by an intermediary (proxy). * * @since 5.0 */ public class LazyLaxLineParser extends BasicLineParser { public final static LazyLaxLineParser INSTANCE = new LazyLaxLineParser(); @Override public Header parseHeader(final CharArrayBuffer buffer) throws ParseException { Args.notNull(buffer, "Char array buffer"); return new BufferedHeader(buffer, false); } } httpcore5/src/main/java/org/apache/hc/core5/http/message/LazyLineParser.java0100644 0000000 0000000 00000004261 00000250600 025730 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; /** * Extension of {@link org.apache.hc.core5.http.message.BasicLineParser} that defers parsing of * header values. Header value is parsed only if accessed with * {@link org.apache.hc.core5.http.Header#getValue()}. *

* This parser should be used to parse request messages on the server server. *

* @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class LazyLineParser extends BasicLineParser { public final static LazyLineParser INSTANCE = new LazyLineParser(); @Override public Header parseHeader(final CharArrayBuffer buffer) throws ParseException { Args.notNull(buffer, "Char array buffer"); return new BufferedHeader(buffer, true); } } httpcore5/src/main/java/org/apache/hc/core5/http/message/LineFormatter.java0100644 0000000 0000000 00000004653 00000250600 025604 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.util.CharArrayBuffer; /** * Interface for formatting elements of the HEAD section of an HTTP message. * There are individual methods for formatting a request line, a * status line, or a header line. The formatting methods are expected to produce * one line of formatted content that does not include a line delimiter * (such as CR-LF). Instances of this interface are expected to be stateless and * thread-safe. * * @since 4.0 */ public interface LineFormatter { /** * Formats a request line. * * @param buffer buffer to write formatted content to. * @param reqline the request line to format */ void formatRequestLine(CharArrayBuffer buffer, RequestLine reqline); /** * Formats a status line. * * @param buffer buffer to write formatted content to. * @param statline the status line to format */ void formatStatusLine(CharArrayBuffer buffer, StatusLine statline); /** * Formats a header. * * @param buffer buffer to write formatted content to. * @param header the header to format */ void formatHeader(CharArrayBuffer buffer, Header header); } httpcore5/src/main/java/org/apache/hc/core5/http/message/LineParser.java0100644 0000000 0000000 00000005471 00000250600 025074 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.util.CharArrayBuffer; /** * Interface for parsing lines in the HEAD section of an HTTP message. * There are individual methods for parsing a request line, a * status line, or a header line. * Instances of this interface are expected to be stateless and thread-safe. * * @since 4.0 */ public interface LineParser { /** * Parses a request line from the given buffer containing one line of text. * * @param buffer a buffer holding a line to parse * * @return the parsed request line * * @throws ParseException in case of a parse error */ RequestLine parseRequestLine(CharArrayBuffer buffer) throws ParseException; /** * Parses a status line from the given buffer containing one line of text. * * @param buffer a buffer holding a line to parse * * @return the parsed status line * * @throws ParseException in case of a parse error */ StatusLine parseStatusLine(CharArrayBuffer buffer) throws ParseException; /** * Parses a header from the given buffer containing one line of text. * The full header line is expected here. Header continuation * lines must be joined by the caller before invoking this method. * * @param buffer a buffer holding the full header line. * * @return the header in the argument buffer. * * @throws ParseException in case of a parse error */ Header parseHeader(CharArrayBuffer buffer) throws ParseException; } httpcore5/src/main/java/org/apache/hc/core5/http/message/MessageSupport.java0100644 0000000 0000000 00000045740 00000250600 026014 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.UnaryOperator; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.FormattedHeader; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HeaderElement; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpMessage; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.MessageHeaders; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; import org.apache.hc.core5.util.Tokenizer; /** * Support methods for HTTP message processing. * * @since 5.0 */ public class MessageSupport { private MessageSupport() { // Do not allow utility class to be instantiated. } /** * @since 5.4 */ public static void formatTokens(final CharArrayBuffer dst, final List tokens, final UnaryOperator transformation) { Args.notNull(dst, "Destination"); if (tokens == null) { return; } for (int i = 0; i < tokens.size(); i++) { final String element = transformation != null ? transformation.apply(tokens.get(i)) : tokens.get(i); if (i > 0) { dst.append(", "); } dst.append(element); } } /** * @since 5.3 */ public static void formatTokens(final CharArrayBuffer dst, final List tokens) { formatTokens(dst, tokens, null); } public static void formatTokens(final CharArrayBuffer dst, final String... tokens) { Args.notNull(dst, "Destination"); boolean first = true; for (final String token : tokens) { if (!first) { dst.append(", "); } dst.append(token); first = false; } } public static void formatTokens(final CharArrayBuffer dst, final Set tokens) { Args.notNull(dst, "Destination"); if (tokens == null) { return; } boolean first = true; for (final String token : tokens) { if (!first) { dst.append(", "); } dst.append(token); first = false; } } /** * @deprecated Use {@link #header(String, Set)} */ @Deprecated public static Header format(final String name, final Set tokens) { return header(name, tokens); } /** * @since 5.4 */ public static Header headerOfTokens(final String name, final List tokens, final UnaryOperator transformation) { Args.notBlank(name, "Header name"); if (tokens == null) { return null; } final CharArrayBuffer buffer = new CharArrayBuffer(256); buffer.append(name); buffer.append(": "); formatTokens(buffer, tokens, transformation); return BufferedHeader.create(buffer); } /** * @since 5.3 */ public static Header headerOfTokens(final String name, final List tokens) { return headerOfTokens(name, tokens, null); } /** * @since 5.3 */ public static Header header(final String name, final Set tokens) { Args.notBlank(name, "Header name"); if (tokens == null) { return null; } final CharArrayBuffer buffer = new CharArrayBuffer(256); buffer.append(name); buffer.append(": "); formatTokens(buffer, tokens); return BufferedHeader.create(buffer); } private static final Tokenizer.Delimiter COMMA = Tokenizer.delimiters(','); /** * @since 5.3 */ public static Header header(final String name, final String... tokens) { Args.notBlank(name, "Header name"); final CharArrayBuffer buffer = new CharArrayBuffer(256); buffer.append(name); buffer.append(": "); formatTokens(buffer, tokens); return BufferedHeader.create(buffer); } /** * @deprecated use {@link #header(String, String...)} */ @Deprecated public static Header format(final String name, final String... tokens) { return headerOfTokens(name, Arrays.asList(tokens)); } /** * @since 5.4 */ public static void parseHeader(final Header header, final BiConsumer consumer) { Args.notNull(header, "Header"); if (header instanceof FormattedHeader) { final CharArrayBuffer buf = ((FormattedHeader) header).getBuffer(); final ParserCursor cursor = new ParserCursor(0, buf.length()); cursor.updatePos(((FormattedHeader) header).getValuePos()); consumer.accept(buf, cursor); } else { final String value = header.getValue(); final ParserCursor cursor = new ParserCursor(0, value.length()); consumer.accept(value, cursor); } } /** * @since 5.4 */ public static void parseHeaders(final MessageHeaders headers, final String name, final BiConsumer consumer) { Args.notNull(headers, "Message headers"); Args.notBlank(name, "Header name"); final Iterator
it = headers.headerIterator(name); while (it.hasNext()) { parseHeader(it.next(), consumer); } } /** * @since 5.4 */ public static void parseElementList(final CharSequence src, final ParserCursor cursor, final BiConsumer consumer) { Args.notNull(src, "Source"); Args.notNull(cursor, "Cursor"); Args.notNull(consumer, "Consumer"); while (!cursor.atEnd()) { consumer.accept(src, cursor); if (!cursor.atEnd()) { final char ch = src.charAt(cursor.getPos()); if (ch == ',') { cursor.updatePos(cursor.getPos() + 1); } } } } /** * @since 5.4 */ public static void parseTokens(final CharSequence src, final ParserCursor cursor, final Tokenizer.Delimiter delimiterPredicate, final Consumer consumer) { parseElementList(src, cursor, (sequence, c) -> { final String token = Tokenizer.INSTANCE.parseToken(src, c, delimiterPredicate); consumer.accept(token); }); } /** * @since 5.3 */ public static void parseTokens(final CharSequence src, final ParserCursor cursor, final Consumer consumer) { parseTokens(src, cursor, COMMA, consumer); } /** * @since 5.4 */ public static void parseTokens(final Header header, final Tokenizer.Delimiter delimiterPredicate, final Consumer consumer) { parseHeader(header, (sequence, cursor) -> parseTokens(sequence, cursor, delimiterPredicate, consumer)); } /** * @since 5.3 */ public static void parseTokens(final Header header, final Consumer consumer) { parseTokens(header, COMMA, consumer); } /** * @since 5.4 */ public static void parseTokens(final MessageHeaders headers, final String headerName, final Tokenizer.Delimiter delimiterPredicate, final Consumer consumer) { parseHeaders(headers, headerName, (sequence, cursor) -> parseTokens(sequence, cursor, delimiterPredicate, consumer)); } /** * @since 5.3 */ public static void parseTokens(final MessageHeaders headers, final String headerName, final Consumer consumer) { parseTokens(headers, headerName, COMMA, consumer); } public static Set parseTokens(final CharSequence src, final ParserCursor cursor) { Args.notNull(src, "Source"); Args.notNull(cursor, "Cursor"); final Set tokens = new LinkedHashSet<>(); parseTokens(src, cursor, tokens::add); return tokens; } public static Set parseTokens(final Header header) { Args.notNull(header, "Header"); final Set tokens = new LinkedHashSet<>(); parseTokens(header, tokens::add); return tokens; } /** * @since 5.3 */ public static Iterator iterateTokens(final MessageHeaders headers, final String name) { Args.notNull(headers, "Message headers"); Args.notBlank(name, "Header name"); return new BasicTokenIterator(headers.headerIterator(name)); } /** * @since 5.3 */ public static void formatElements(final CharArrayBuffer dst, final List elements) { Args.notNull(dst, "Destination"); if (elements == null) { return; } for (int i = 0; i < elements.size(); i++) { final HeaderElement element = elements.get(i); if (i > 0) { dst.append(", "); } BasicHeaderValueFormatter.INSTANCE.formatHeaderElement(dst, element, false); } } /** * @since 5.3 */ public static void formatElements(final CharArrayBuffer dst, final HeaderElement... elements) { formatElements(dst, Arrays.asList(elements)); } /** * @since 5.3 */ public static Header headerOfElements(final String name, final List elements) { Args.notBlank(name, "Header name"); if (elements == null) { return null; } final CharArrayBuffer buffer = new CharArrayBuffer(256); buffer.append(name); buffer.append(": "); formatElements(buffer, elements); return BufferedHeader.create(buffer); } /** * @since 5.3 */ public static Header header(final String name, final HeaderElement... elements) { Args.notBlank(name, "Header name"); final CharArrayBuffer buffer = new CharArrayBuffer(256); buffer.append(name); buffer.append(": "); formatElements(buffer, elements); return BufferedHeader.create(buffer); } /** * @since 5.3 */ public static void parseElements(final CharSequence buffer, final ParserCursor cursor, final Consumer consumer) { parseElementList(buffer, cursor, (sequence, c) -> { final HeaderElement element = BasicHeaderValueParser.INSTANCE.parseHeaderElement(buffer, cursor); consumer.accept(element); }); } /** * @since 5.3 */ public static void parseElements(final Header header, final Consumer consumer) { Args.notNull(header, "Header"); parseHeader(header, (sequence, cursor) -> parseElements(sequence, cursor, consumer)); } /** * @since 5.3 */ public static void parseElements(final MessageHeaders headers, final String headerName, final Consumer consumer) { Args.notNull(headers, "Headers"); parseHeaders(headers, headerName, (sequence, cursor) -> parseElements(sequence, cursor, consumer)); } /** * @deprecated Use {@link #parseElements(Header, Consumer)} */ @Deprecated public static HeaderElement[] parse(final Header header) { final List elements = new ArrayList<>(); parseElements(header, elements::add); return elements.toArray(new HeaderElement[]{}); } /** * @since 5.3 */ public static List parseElements(final Header header) { final List elements = new ArrayList<>(); parseElements(header, elements::add); return elements; } public static Iterator iterate(final MessageHeaders headers, final String name) { Args.notNull(headers, "Message headers"); Args.notBlank(name, "Header name"); return new BasicHeaderElementIterator(headers.headerIterator(name)); } /** * @since 5.3 */ public static void formatParameters(final CharArrayBuffer dst, final List params) { Args.notNull(dst, "Destination"); if (params == null) { return; } for (int i = 0; i < params.size(); i++) { final NameValuePair param = params.get(i); if (i > 0) { dst.append("; "); } BasicHeaderValueFormatter.INSTANCE.formatNameValuePair(dst, param, false); } } /** * @since 5.3 */ public static void formatParameters(final CharArrayBuffer dst, final NameValuePair... params) { Args.notNull(dst, "Destination"); if (params == null) { return; } boolean first = true; for (final NameValuePair param : params) { if (!first) { dst.append("; "); } BasicHeaderValueFormatter.INSTANCE.formatNameValuePair(dst, param, false); first = false; } } /** * @since 5.3 */ public static void parseParameters(final CharSequence src, final ParserCursor cursor, final Consumer consumer) { Args.notNull(src, "Source"); Args.notNull(cursor, "Cursor"); Args.notNull(consumer, "Consumer"); while (!cursor.atEnd()) { final NameValuePair param = BasicHeaderValueParser.INSTANCE.parseNameValuePair(src, cursor); consumer.accept(param); if (!cursor.atEnd()) { final char ch = src.charAt(cursor.getPos()); if (ch == ';') { cursor.updatePos(cursor.getPos() + 1); } if (ch == ',') { break; } } } } public static void addContentTypeHeader(final HttpMessage message, final EntityDetails entity) { if (entity != null && entity.getContentType() != null && !message.containsHeader(HttpHeaders.CONTENT_TYPE)) { message.addHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, entity.getContentType())); } } public static void addContentEncodingHeader(final HttpMessage message, final EntityDetails entity) { if (entity != null && entity.getContentEncoding() != null && !message.containsHeader(HttpHeaders.CONTENT_ENCODING)) { message.addHeader(new BasicHeader(HttpHeaders.CONTENT_ENCODING, entity.getContentEncoding())); } } public static void addTrailerHeader(final HttpMessage message, final EntityDetails entity) { if (entity != null && !message.containsHeader(HttpHeaders.TRAILER)) { final Set trailerNames = entity.getTrailerNames(); if (trailerNames != null && !trailerNames.isEmpty()) { message.setHeader(MessageSupport.header(HttpHeaders.TRAILER, trailerNames)); } } } /** * @since 5.4 */ public static boolean canResponseHaveBody(final HttpResponse response) { final int status = response.getCode(); return status >= HttpStatus.SC_SUCCESS && status != HttpStatus.SC_NO_CONTENT && status != HttpStatus.SC_NOT_MODIFIED; } /** * @since 5.0 */ public static boolean canResponseHaveBody(final String method, final HttpResponse response) { if (Method.HEAD.isSame(method)) { return false; } final int status = response.getCode(); if (Method.CONNECT.isSame(method) && status == HttpStatus.SC_OK) { return false; } return canResponseHaveBody(response); } private final static Set HOP_BY_HOP; static { final TreeSet set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); set.add(HttpHeaders.CONNECTION); set.add(HttpHeaders.CONTENT_LENGTH); set.add(HttpHeaders.TRANSFER_ENCODING); set.add(HttpHeaders.HOST); set.add(HttpHeaders.KEEP_ALIVE); set.add(HttpHeaders.TE); set.add(HttpHeaders.UPGRADE); set.add(HttpHeaders.PROXY_AUTHORIZATION); set.add("Proxy-Authentication-Info"); set.add(HttpHeaders.PROXY_AUTHENTICATE); HOP_BY_HOP = Collections.unmodifiableSet(set); } /** * @since 5.3 */ public static boolean isHopByHop(final String headerName) { if (headerName == null) { return false; } return HOP_BY_HOP.contains(headerName); } /** * @since 5.3 */ public static Set hopByHopConnectionSpecific(final MessageHeaders headers) { final Header connectionHeader = headers.getFirstHeader(HttpHeaders.CONNECTION); final String connDirective = connectionHeader != null ? connectionHeader.getValue() : null; // Disregard most common 'Close' and 'Keep-Alive' tokens if (connDirective != null && !connDirective.equalsIgnoreCase(HeaderElements.CLOSE) && !connDirective.equalsIgnoreCase(HeaderElements.KEEP_ALIVE)) { final TreeSet result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); result.addAll(HOP_BY_HOP); result.addAll(parseTokens(connectionHeader)); return result; } return HOP_BY_HOP; } } httpcore5/src/main/java/org/apache/hc/core5/http/message/ParserCursor.java0100644 0000000 0000000 00000003235 00000250600 025456 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.util.Tokenizer; /** * This class represents a context of a parsing operation: *
    *
  • the current position the parsing operation is expected to start at
  • *
  • the bounds limiting the scope of the parsing operation
  • *
* * @since 4.0 */ public class ParserCursor extends Tokenizer.Cursor { public ParserCursor(final int lowerBound, final int upperBound) { super(lowerBound, upperBound); } } httpcore5/src/main/java/org/apache/hc/core5/http/message/RequestLine.java0100644 0000000 0000000 00000005663 00000250600 025273 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.io.Serializable; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.util.Args; /** * HTTP/1.1 request line. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public final class RequestLine implements Serializable { private static final long serialVersionUID = 2810581718468737193L; private final ProtocolVersion protoversion; private final String method; private final String uri; public RequestLine(final HttpRequest request) { super(); Args.notNull(request, "Request"); this.method = request.getMethod(); this.uri = request.getRequestUri(); this.protoversion = request.getVersion() != null ? request.getVersion() : HttpVersion.HTTP_1_1; } public RequestLine(final String method, final String uri, final ProtocolVersion version) { super(); this.method = Args.notNull(method, "Method"); this.uri = Args.notNull(uri, "URI"); this.protoversion = version != null ? version : HttpVersion.HTTP_1_1; } public String getMethod() { return this.method; } public ProtocolVersion getProtocolVersion() { return this.protoversion; } public String getUri() { return this.uri; } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append(this.method).append(" ").append(this.uri).append(" ").append(this.protoversion); return buf.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/message/StatusLine.java0100644 0000000 0000000 00000017026 00000250600 025122 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.io.Serializable; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.util.Args; /** * HTTP/1.1 status line. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public final class StatusLine implements Serializable { private static final long serialVersionUID = -2443303766890459269L; /** * The protocol version. */ private final ProtocolVersion protoVersion; /** * The status code. */ private final int statusCode; /** * The status code class. */ private final StatusClass statusClass; /** * The reason phrase. */ private final String reasonPhrase; public StatusLine(final HttpResponse response) { super(); Args.notNull(response, "Response"); this.protoVersion = response.getVersion() != null ? response.getVersion() : HttpVersion.HTTP_1_1; this.statusCode = response.getCode(); this.statusClass = StatusClass.from(this.statusCode); this.reasonPhrase = response.getReasonPhrase(); } /** * Creates a new status line with the given version, status, and reason. * * @param version the protocol version of the response * @param statusCode the status code of the response * @param reasonPhrase the reason phrase to the status code, or * {@code null} */ public StatusLine(final ProtocolVersion version, final int statusCode, final String reasonPhrase) { super(); this.statusCode = Args.notNegative(statusCode, "Status code"); this.statusClass = StatusClass.from(this.statusCode); this.protoVersion = version != null ? version : HttpVersion.HTTP_1_1; this.reasonPhrase = reasonPhrase; } public int getStatusCode() { return this.statusCode; } public StatusClass getStatusClass() { return this.statusClass; } /** * Whether this status code is in the HTTP series {@link StatusClass#INFORMATIONAL}. * * @return Whether this status code's class is {@link StatusClass#INFORMATIONAL}. * @since 5.1 */ public boolean isInformational() { return getStatusClass() == StatusClass.INFORMATIONAL; } /** * Whether this status code is in the HTTP series {@link StatusClass#SUCCESSFUL}. * * @return Whether this status code's class is {@link StatusClass#SUCCESSFUL}. * @since 5.1 */ public boolean isSuccessful() { return getStatusClass() == StatusClass.SUCCESSFUL; } /** * Whether this status code is in the HTTP series {@link StatusClass#REDIRECTION}. * * @return Whether this status code's class is {@link StatusClass#REDIRECTION}. * @since 5.1 */ public boolean isRedirection() { return getStatusClass() == StatusClass.REDIRECTION; } /** * Whether this status code is in the HTTP series {@link StatusClass#CLIENT_ERROR}. * * @return Whether this status code's class is {@link StatusClass#CLIENT_ERROR}. * @since 5.1 */ public boolean isClientError() { return getStatusClass() == StatusClass.CLIENT_ERROR; } /** * Whether this status code is in the HTTP series {@link StatusClass#SERVER_ERROR}. * * @return Whether this status code's class is {@link StatusClass#SERVER_ERROR}. * @since 5.1 */ public boolean isServerError() { return getStatusClass() == StatusClass.SERVER_ERROR; } /** * Whether this status code is in the HTTP series {@link StatusClass#CLIENT_ERROR} * or {@link StatusClass#SERVER_ERROR}. * * @return Whether this status code's class is {@link StatusClass#CLIENT_ERROR} or {@link StatusClass#SERVER_ERROR}. * @since 5.1 */ public boolean isError() { return isClientError() || isServerError(); } public ProtocolVersion getProtocolVersion() { return this.protoVersion; } public String getReasonPhrase() { return this.reasonPhrase; } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append(this.protoVersion).append(" ").append(this.statusCode).append(" "); if (this.reasonPhrase != null) { buf.append(this.reasonPhrase); } return buf.toString(); } /** * Standard classes of HTTP status codes, plus {@code OTHER} for non-standard codes. * @see org.apache.hc.core5.http.HttpStatus */ public enum StatusClass { /** * Informational {@code 1xx} HTTP status codes. */ INFORMATIONAL, /** * Successful {@code 2xx} HTTP status codes. */ SUCCESSFUL, /** * Redirection {@code 3xx} HTTP status codes. */ REDIRECTION, /** * Client Error {@code 4xx} HTTP status codes. */ CLIENT_ERROR, /** * {@code 5xx} HTTP status codes. */ SERVER_ERROR, /** * Any other HTTP status codes (e.g. non-standard status codes). */ OTHER; /** * Gets the response status class for the given status code. * * @param statusCode response status code to get the class for. * @return class of the response status code. * @see org.apache.hc.core5.http.HttpStatus */ public static StatusClass from(final int statusCode) { final StatusClass statusClass; switch (statusCode / 100) { case 1: statusClass = INFORMATIONAL; break; case 2: statusClass = SUCCESSFUL; break; case 3: statusClass = REDIRECTION; break; case 4: statusClass = CLIENT_ERROR; break; case 5: statusClass = SERVER_ERROR; break; default: statusClass = OTHER; break; } return statusClass; } } } httpcore5/src/main/java/org/apache/hc/core5/http/message/TokenParser.java0100644 0000000 0000000 00000010571 00000250600 025262 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.util.BitSet; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.util.Tokenizer; /** * Low level parser for header field elements. The parsing routines of this class are designed * to produce near zero intermediate garbage and make no intermediate copies of input data. *

* This class is immutable and thread safe. * * @since 4.4 * * @deprecated Use {@link Tokenizer} */ @Deprecated @Contract(threading = ThreadingBehavior.IMMUTABLE) public class TokenParser extends Tokenizer { public static final TokenParser INSTANCE = new TokenParser(); /** Double quote */ public static final char DQUOTE = '\"'; /** Backward slash / escape character */ public static final char ESCAPE = '\\'; public String parseToken(final CharSequence buf, final ParserCursor cursor, final BitSet delimiters) { return super.parseToken(buf, cursor, delimiters); } public String parseValue(final CharSequence buf, final ParserCursor cursor, final BitSet delimiters) { return super.parseValue(buf, cursor, delimiters); } public void skipWhiteSpace(final CharSequence buf, final ParserCursor cursor) { super.skipWhiteSpace(buf, cursor); } public void copyContent(final CharSequence buf, final ParserCursor cursor, final BitSet delimiters, final StringBuilder dst) { super.copyContent(buf, cursor, delimiters, dst); } @Override public void copyContent(final CharSequence buf, final Tokenizer.Cursor cursor, final BitSet delimiters, final StringBuilder dst) { final ParserCursor parserCursor = new ParserCursor(cursor.getLowerBound(), cursor.getUpperBound()); parserCursor.updatePos(cursor.getPos()); copyContent(buf, parserCursor, delimiters, dst); cursor.updatePos(parserCursor.getPos()); } public void copyUnquotedContent(final CharSequence buf, final ParserCursor cursor, final BitSet delimiters, final StringBuilder dst) { super.copyUnquotedContent(buf, cursor, delimiters, dst); } @Override public void copyUnquotedContent(final CharSequence buf, final Tokenizer.Cursor cursor, final BitSet delimiters, final StringBuilder dst) { final ParserCursor parserCursor = new ParserCursor(cursor.getLowerBound(), cursor.getUpperBound()); parserCursor.updatePos(cursor.getPos()); copyUnquotedContent(buf, parserCursor, delimiters, dst); cursor.updatePos(parserCursor.getPos()); } public void copyQuotedContent(final CharSequence buf, final ParserCursor cursor, final StringBuilder dst) { super.copyQuotedContent(buf, cursor, dst); } @Override public void copyQuotedContent(final CharSequence buf, final Tokenizer.Cursor cursor, final StringBuilder dst) { final ParserCursor parserCursor = new ParserCursor(cursor.getLowerBound(), cursor.getUpperBound()); parserCursor.updatePos(cursor.getPos()); copyQuotedContent(buf, parserCursor, dst); cursor.updatePos(parserCursor.getPos()); } } httpcore5/src/main/java/org/apache/hc/core5/http/message/package-info.java0100644 0000000 0000000 00000002501 00000250600 025343 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Core HTTP message components, message element parser * and writer APIs and their default implementations. */ package org.apache.hc.core5.http.message; httpcore5/src/main/java/org/apache/hc/core5/http/nio/0040755 0000000 0000000 00000000000 00000250600 021322 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncClientEndpoint.java0100644 0000000 0000000 00000012342 00000250600 026101 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.util.concurrent.Future; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.concurrent.BasicFuture; import org.apache.hc.core5.concurrent.CompletingFutureContribution; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.nio.support.BasicClientExchangeHandler; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; /** * Client endpoint leased from a connection manager. *

* Once the endpoint is no longer needed it MUST be released with {@link #releaseAndReuse()} * or {@link #releaseAndDiscard()}. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE) public abstract class AsyncClientEndpoint { /** * Initiates a message exchange using the given handler. *

* Once the endpoint is no longer needed it MUST be released with {@link #releaseAndReuse()} * or {@link #releaseAndDiscard()}. */ public abstract void execute( AsyncClientExchangeHandler exchangeHandler, HandlerFactory pushHandlerFactory, HttpContext context); /** * Initiates a message exchange using the given handler. *

* Once the endpoint is no longer needed it MUST be released with {@link #releaseAndReuse()} * or {@link #releaseAndDiscard()}. */ public void execute( final AsyncClientExchangeHandler exchangeHandler, final HttpContext context) { execute(exchangeHandler, null, context); } /** * Releases the underlying connection back to the connection pool as re-usable. */ public abstract void releaseAndReuse(); /** * Shuts down the underlying connection and removes it from the connection pool. */ public abstract void releaseAndDiscard(); /** * Determines if the connection to the remote endpoint is still open and valid. */ public abstract boolean isConnected(); /** * Initiates message exchange using the given request producer and response consumer. *

* Once the endpoint is no longer needed it MUST be released with {@link #releaseAndReuse()} * or {@link #releaseAndDiscard()}. */ public final Future execute( final AsyncRequestProducer requestProducer, final AsyncResponseConsumer responseConsumer, final HandlerFactory pushHandlerFactory, final HttpContext context, final FutureCallback callback) { final BasicFuture future = new BasicFuture<>(callback); execute(new BasicClientExchangeHandler<>(requestProducer, responseConsumer, new CompletingFutureContribution(future)), pushHandlerFactory, context != null ? context : HttpCoreContext.create()); return future; } /** * Initiates message exchange using the given request producer and response consumer. *

* Once the endpoint is no longer needed it MUST be released with {@link #releaseAndReuse()} * or {@link #releaseAndDiscard()}. */ public final Future execute( final AsyncRequestProducer requestProducer, final AsyncResponseConsumer responseConsumer, final HttpContext context, final FutureCallback callback) { return execute(requestProducer, responseConsumer, null, context, callback); } /** * Initiates a message exchange using the given request producer and response consumer. *

* Once the endpoint is no longer needed it MUST be released with {@link #releaseAndReuse()} * or {@link #releaseAndDiscard()}. */ public final Future execute( final AsyncRequestProducer requestProducer, final AsyncResponseConsumer responseConsumer, final FutureCallback callback) { return execute(requestProducer, responseConsumer, null, null, callback); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncClientExchangeHandler.java0100644 0000000 0000000 00000006526 00000250600 027350 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.protocol.HttpContext; /** * Abstract asynchronous client side message exchange handler that acts as a request producer * and a response consumer. * * @since 5.0 */ public interface AsyncClientExchangeHandler extends AsyncDataExchangeHandler { /** * Triggered to signal the ability of the underlying request channel * to accept a request messages. The data producer can choose to send * a request message immediately inside the call or asynchronously * at some later point. * * @param channel the request channel capable to accepting a request message. * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void produceRequest(RequestChannel channel, HttpContext context) throws HttpException, IOException; /** * Triggered to signal receipt of a response message head. * * @param response the response message head. * @param entityDetails the response entity details or {@code null} if the response * does not enclose an entity. * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void consumeResponse(HttpResponse response, EntityDetails entityDetails, HttpContext context) throws HttpException, IOException; /** * Triggered to signal receipt of an intermediate (1xx) HTTP response. * * @param response the intermediate (1xx) HTTP response. * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void consumeInformation(HttpResponse response, HttpContext context) throws HttpException, IOException; /** * Triggered to cancel the message exchange. */ void cancel(); } httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncDataConsumer.java0100644 0000000 0000000 00000005477 00000250600 025562 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; /** * Abstract asynchronous data consumer. * * @since 5.0 */ public interface AsyncDataConsumer extends ResourceHolder { /** * Triggered to signal ability of the underlying data stream to receive * data capacity update. The data consumer can choose to write data * immediately inside the call or asynchronously at some later point. * * @param capacityChannel the channel for capacity updates. * @throws IOException in case of an I/O error. */ void updateCapacity(CapacityChannel capacityChannel) throws IOException; /** * Triggered to pass incoming data to the data consumer. The consumer must * consume the entire content of the data buffer. The consumer must stop * incrementing its capacity on the capacity channel if it is unable to * accept more data. Once the data consumer has handled accumulated data * or allocated more intermediate storage it can update its capacity * information on the capacity channel. * * @param src data source. * @throws IOException in case of an I/O error. */ void consume(ByteBuffer src) throws IOException; /** * Triggered to signal termination of the data stream. * * @param trailers data stream trailers. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void streamEnd(List trailers) throws HttpException, IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncDataExchangeHandler.java0100644 0000000 0000000 00000003101 00000250600 026765 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; /** * Abstract asynchronous data exchange handler that acts as a data consumer * and a data producer. * * @since 5.0 */ public interface AsyncDataExchangeHandler extends AsyncDataConsumer, AsyncDataProducer { /** * Triggered to signal a failure in data processing. * * @param cause the cause of the failure. */ void failed(Exception cause); } httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncDataProducer.java0100644 0000000 0000000 00000005257 00000250600 025546 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; /** * Abstract asynchronous data producer. * * @since 5.0 */ public interface AsyncDataProducer extends ResourceHolder { /** * Returns the number of bytes immediately available for output. * This method can be used as a hint to control output events * of the underlying I/O session. *

* Please note this method should return zero if the data producer * is unable to produce any more data, in which case * {@link #produce(DataStreamChannel)} method will not get triggered. * The producer can resume writing out data asynchronously * once more data becomes available or request output readiness events * with {@link DataStreamChannel#requestOutput()}. * * @return the number of bytes immediately available for output * * @see #produce(DataStreamChannel) * @see DataStreamChannel#requestOutput() */ int available(); /** * Triggered to signal the ability of the underlying data channel * to accept more data. The data producer can choose to write data * immediately inside the call or asynchronously at some later point. *

* Please note this method gets triggered only if {@link #available()} * returns a positive value. * * @param channel the data channel capable of accepting more data. * @throws IOException in case of an I/O error. * @see #available() */ void produce(DataStreamChannel channel) throws IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncEntityConsumer.java0100644 0000000 0000000 00000004566 00000250600 026163 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; /** * Abstract asynchronous message entity consumer. * * @param entity representation. * * @since 5.0 */ public interface AsyncEntityConsumer extends AsyncDataConsumer { /** * Signals beginning of an incoming request entity stream. * * @param entityDetails the details of the incoming message entity. * @param resultCallback the result callback. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void streamStart(EntityDetails entityDetails, FutureCallback resultCallback) throws HttpException, IOException; /** * Triggered to signal a failure in data processing. * * @param cause the cause of the failure. */ void failed(Exception cause); /** * Returns the result of entity processing when it becomes available or {@code null} * if the entity is still being received. * * @return the response processing result. */ T getContent(); } httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncEntityProducer.java0100644 0000000 0000000 00000003372 00000250600 026145 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import org.apache.hc.core5.http.EntityDetails; /** * Abstract asynchronous message entity producer. * * @since 5.0 */ public interface AsyncEntityProducer extends AsyncDataProducer, EntityDetails { /** * Determines whether the producer can consistently produce the same content * after invocation of {@link ResourceHolder#releaseResources()}. */ boolean isRepeatable(); /** * Triggered to signal a failure in data generation. * * @param cause the cause of the failure. */ void failed(Exception cause); } httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncFilterChain.java0100644 0000000 0000000 00000007646 00000250600 025365 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.protocol.HttpContext; /** * AsyncFilterChain represents a single element in the server side request processing chain. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.STATELESS) public interface AsyncFilterChain { /** * Response trigger that can be used to generate the final HTTP response * and terminate HTTP request processing. */ interface ResponseTrigger { /** * Sends an intermediate informational HTTP response to the client. * * @param response the intermediate (1xx) HTTP response. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void sendInformation(HttpResponse response) throws HttpException, IOException; /** * Sends a final HTTP response to the client. * * @param response the final (non 1xx) HTTP response. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void submitResponse(HttpResponse response, AsyncEntityProducer entityProducer) throws HttpException, IOException; /** * Pushes a request message head as a promise to deliver a response message. * * @param promise the request message header used as a promise. * @param responseProducer the push response message producer. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void pushPromise(HttpRequest promise, AsyncPushProducer responseProducer) throws HttpException, IOException; } /** * Proceeds to the next element in the request processing chain. * * @param request the actual request. * @param entityDetails the request entity details or {@code null} if the request * does not enclose an entity. * @param responseTrigger the response trigger. * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ AsyncDataConsumer proceed( HttpRequest request, EntityDetails entityDetails, HttpContext context, ResponseTrigger responseTrigger) throws HttpException, IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncFilterHandler.java0100644 0000000 0000000 00000006231 00000250600 025705 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.protocol.HttpContext; /** * AsyncFilterHandler represents a routine for handling all incoming requests * in the server side request processing chain. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.STATELESS) public interface AsyncFilterHandler { /** * Processes the incoming HTTP request and if processing has been completed * submits a final response to the client. The handler can choose to send * response messages immediately inside the call or asynchronously at some later point. * The handler must not use the response trigger after passing control to the next filter * with the * {@link AsyncFilterChain#proceed(HttpRequest, EntityDetails, HttpContext, AsyncFilterChain.ResponseTrigger)} * method. * * @param request the actual request head. * @param entityDetails the request entity details or {@code null} if the request * does not enclose an entity. * @param context the actual execution context. * @param responseTrigger the response trigger. * @param chain the next element in the request processing chain. * @return the data consumer to be used to process incoming request data. It is * expected to be {@code null} if entityDetails parameter is {@code null}. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ AsyncDataConsumer handle( HttpRequest request, EntityDetails entityDetails, HttpContext context, AsyncFilterChain.ResponseTrigger responseTrigger, AsyncFilterChain chain) throws HttpException, IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncPushConsumer.java0100644 0000000 0000000 00000004771 00000250600 025624 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.protocol.HttpContext; /** * Abstract asynchronous push response consumer. * * @since 5.0 */ public interface AsyncPushConsumer extends AsyncDataConsumer { /** * Triggered to signal receipt of a request message head used as a promise * and the corresponding pushed response. * * @param promise the request message head used as a promise. * @param response the pushed response message. * @param entityDetails the response entity details or {@code null} if the response * does not enclose an entity. * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void consumePromise(HttpRequest promise, HttpResponse response, EntityDetails entityDetails, HttpContext context) throws HttpException, IOException; /** * Triggered to signal a failure in data processing. * * @param cause the cause of the failure. */ void failed(Exception cause); } httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncPushProducer.java0100644 0000000 0000000 00000004505 00000250600 025607 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.protocol.HttpContext; /** * Abstract asynchronous push response producer. * * @since 5.0 */ public interface AsyncPushProducer extends AsyncDataProducer { /** * Triggered to signal the ability of the underlying response channel * to accept response messages. The data producer can choose to send * a final response message immediately inside the call or asynchronously * at some later point. The final response can be preceded by a number * of intermediate messages. * * @param channel the response channel capable to accepting response messages. * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void produceResponse(ResponseChannel channel, HttpContext context) throws HttpException, IOException; /** * Triggered to signal a failure in data generation. * * @param cause the cause of the failure. */ void failed(Exception cause); } httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncRequestConsumer.java0100644 0000000 0000000 00000005145 00000250600 026331 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.protocol.HttpContext; /** * Abstract asynchronous request consumer. * * @param the future result type returned for callbacks. * * @since 5.0 */ public interface AsyncRequestConsumer extends AsyncDataConsumer { /** * Triggered to signal receipt of a request message head. * * @param request the request message head. * @param entityDetails the request entity details or {@code null} if the request * does not enclose an entity. * @param context the actual execution context. * @param resultCallback the result callback called when request processing * has been completed successfully or unsuccessfully. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void consumeRequest(HttpRequest request, EntityDetails entityDetails, HttpContext context, FutureCallback resultCallback) throws HttpException, IOException; /** * Triggered to signal a failure in data processing. * * @param cause the cause of the failure. */ void failed(Exception cause); } httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncRequestProducer.java0100644 0000000 0000000 00000004644 00000250600 026324 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.protocol.HttpContext; /** * Abstract asynchronous request producer. * * @since 5.0 */ public interface AsyncRequestProducer extends AsyncDataProducer { /** * Triggered to signal the ability of the underlying request channel * to accept a request messages. The data producer can choose to send * a request message immediately inside the call or asynchronously * at some later point. * * @param channel the request channel capable to accepting a request message. * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void sendRequest(RequestChannel channel, HttpContext context) throws HttpException, IOException; /** * Tests whether the producer can consistently produce the same content * after invocation of {@link ResourceHolder#releaseResources()}. */ boolean isRepeatable(); /** * Triggered to signal a failure in data generation. * * @param cause the cause of the failure. */ void failed(Exception cause); } httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncResponseConsumer.java0100644 0000000 0000000 00000006024 00000250600 026474 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.protocol.HttpContext; /** * Abstract asynchronous response consumer. * * @param response representation. * * @since 5.0 */ public interface AsyncResponseConsumer extends AsyncDataConsumer { /** * Triggered to signal receipt of a response message head. * * @param response the response message head. * @param entityDetails the response entity details or {@code null} if the response * does not enclose an entity. * @param context the actual execution context. * @param resultCallback the result callback called when response processing * has been completed successfully or unsuccessfully. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void consumeResponse(HttpResponse response, EntityDetails entityDetails, HttpContext context, FutureCallback resultCallback) throws HttpException, IOException; /** * Triggered to signal receipt of an intermediate (1xx) HTTP response. * * @param response the intermediate (1xx) HTTP response. * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void informationResponse(HttpResponse response, HttpContext context) throws HttpException, IOException; /** * Triggered to signal a failure in data processing. * * @param cause the cause of the failure. */ void failed(Exception cause); } httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncResponseProducer.java0100644 0000000 0000000 00000004352 00000250600 026466 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.protocol.HttpContext; /** * Abstract asynchronous response producer. * * @since 5.0 */ public interface AsyncResponseProducer extends AsyncDataProducer { /** * Triggered to signal the ability of the underlying response channel * to accept response messages. The data producer can choose to send * response messages immediately inside the call or asynchronously * at some later point. * * @param channel the response channel capable to accepting response messages. * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void sendResponse(ResponseChannel channel, HttpContext context) throws HttpException, IOException; /** * Triggered to signal a failure in data generation. * * @param cause the cause of the failure. */ void failed(Exception cause); } httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncServerExchangeHandler.java0100644 0000000 0000000 00000004672 00000250600 027400 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.protocol.HttpContext; /** * Abstract asynchronous server side message exchange handler that acts as a request consumer * and a response producer. * * @since 5.0 */ public interface AsyncServerExchangeHandler extends AsyncDataExchangeHandler { /** * Processes the actual HTTP request. The handler can choose to send * response messages immediately inside the call or asynchronously * at some later point. * * @param request the actual request. * @param entityDetails the request entity details or {@code null} if the request * does not enclose an entity. * @param responseChannel the response channel. * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void handleRequest( HttpRequest request, EntityDetails entityDetails, ResponseChannel responseChannel, HttpContext context) throws HttpException, IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncServerRequestHandler.java0100644 0000000 0000000 00000012515 00000250600 027301 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.protocol.HttpContext; /** * AsyncServerRequestHandler represents a routine for processing of a specific group * of HTTP requests. Request execution filters are designed to take care of protocol * specific aspects, whereas individual request handlers are expected to take care * of application specific HTTP processing. The main purpose of a request handler * is to generate a response object with a content entity to be sent back to * the client in response to the given request. * * @param request representation. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.STATELESS) public interface AsyncServerRequestHandler { /** * Response trigger that can be used to submit a final HTTP response * and terminate HTTP request processing. */ interface ResponseTrigger { /** * Sends an intermediate informational HTTP response to the client. * * @param response the intermediate (1xx) HTTP response * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void sendInformation(HttpResponse response, HttpContext context) throws HttpException, IOException; /** * Sends a final HTTP response to the client. * * @param responseProducer the HTTP response message producer. * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void submitResponse(AsyncResponseProducer responseProducer, HttpContext context) throws HttpException, IOException; /** * Pushes a request message head as a promise to deliver a response message. * * @param promise the request message header used as a promise. * @param context the actual execution context. * @param responseProducer the push response message producer. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void pushPromise(HttpRequest promise, HttpContext context, AsyncPushProducer responseProducer) throws HttpException, IOException; } /** * Triggered to signal new incoming request. The handler can create a {@link AsyncRequestConsumer} based on * properties of the request head and entity details and let it process the request data stream. The request * handler will be used to generate an object that represents request data. * * @param request the incoming request head. * @param entityDetails the request entity details or {@code null} if the request * does not enclose an entity. * @param context the actual execution context. * @return the request handler. * @throws HttpException in case of an HTTP protocol violation. */ AsyncRequestConsumer prepare(HttpRequest request, EntityDetails entityDetails, HttpContext context) throws HttpException; /** * Triggered to handles the request object produced by the {@link AsyncRequestConsumer} returned * from the {@link #prepare(HttpRequest, EntityDetails, HttpContext)} method. The handler can choose * to send response messages immediately inside the call or asynchronously at some later point. * * @param requestObject the request object. * @param responseTrigger the response trigger. * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void handle(T requestObject, ResponseTrigger responseTrigger, HttpContext context) throws HttpException, IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/nio/CapacityChannel.java0100644 0000000 0000000 00000003666 00000250600 025223 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; /** * Abstract capacity update channel. *

* Implementations are expected to be thread-safe. *

* * @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE) public interface CapacityChannel { /** * Updates data capacity information through this channel. The total number of * bytes the consumer is capable of accepting is incremented * by the given increment number. * * @param increment non-negative number of extra bytes the consumer * can accept. * @throws IOException in case of an I/O error. */ void update(int increment) throws IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/nio/ContentDecoder.java0100644 0000000 0000000 00000004521 00000250600 025064 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import org.apache.hc.core5.http.Header; /** * Abstract HTTP content decoder. HTTP content decoders can be used * to read entity content from the underlying channel in small * chunks and apply the required coding transformation. * * @since 4.0 */ public interface ContentDecoder { /** * Reads a portion of content from the underlying channel * * @param dst The buffer into which entity content is to be transferred * @return The number of bytes read, possibly zero, or -1 if the * channel has reached end-of-stream * @throws IOException if I/O error occurs while reading content */ int read(ByteBuffer dst) throws IOException; /** * Returns {@code true} if the entity has been received in its * entirety. * * @return {@code true} if all the content has been consumed, * {@code false} otherwise. */ boolean isCompleted(); /** * Returns content trailers if available * * @return list of trailers * * @since 5.0 */ List getTrailers(); } httpcore5/src/main/java/org/apache/hc/core5/http/nio/ContentEncoder.java0100644 0000000 0000000 00000004475 00000250600 025106 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import org.apache.hc.core5.http.Header; /** * Abstract HTTP content encoder. HTTP content encoders can be used * to apply the required coding transformation and write entity * content to the underlying channel in small chunks. * * @since 4.0 */ public interface ContentEncoder { /** * Writes a portion of entity content to the underlying channel. * * @param src The buffer from which content is to be retrieved * @return The number of bytes read, possibly zero * @throws IOException if I/O error occurs while writing content */ int write(ByteBuffer src) throws IOException; /** * Terminates the content stream. * * @throws IOException if I/O error occurs while writing content */ void complete(List trailers) throws IOException; /** * Returns {@code true} if the entity has been transferred in its * entirety. * * @return {@code true} if all the content has been produced, * {@code false} otherwise. */ boolean isCompleted(); } httpcore5/src/main/java/org/apache/hc/core5/http/nio/DataStreamChannel.java0100644 0000000 0000000 00000005525 00000250600 025507 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.Header; /** * Abstract byte stream channel. *

* Implementations are expected to be thread-safe. *

* * @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE) public interface DataStreamChannel extends StreamChannel { /** * Signals intent by the data producer to produce more data. * Once the channel is able to accept data its handler is expected * to trigger an event to notify the data producer. */ void requestOutput(); /** * Writes data from the buffer through this channel into the underlying byte stream. * If the underlying byte stream is temporarily unable to accept more data * it can return zero to indicate that no data could be written to the data * stream. The data producer can choose to call {@link #requestOutput()} * to signal its intent to produce more data. * * @param src source of data * * @return The number of bytes written, possibly zero */ @Override int write(ByteBuffer src) throws IOException; /** * Terminates the underlying data stream and optionally writes * a closing sequence with the given trailers. *

* Please note that some data streams may not support trailers * and may silently ignore the trailers parameter. *

* * @throws IOException in case of an I/O error. */ void endStream(List trailers) throws IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/nio/FileContentDecoder.java0100644 0000000 0000000 00000004654 00000250600 025673 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import java.nio.channels.FileChannel; /** * A content decoder capable of transferring data directly to a {@link FileChannel} * * @since 4.0 */ public interface FileContentDecoder extends ContentDecoder { /** * Transfers a portion of entity content from the underlying network channel * into the given file channel. *

* Warning: Many implementations cannot write beyond the length of the file. * If the position exceeds the channel's size, some implementations * may throw an IOException. *

* * @param dst the target FileChannel to transfer data into. * @param position * The position within the file at which the transfer is to begin; * must be non-negative. * Must be less than or equal to the size of the file * @param count * The maximum number of bytes to be transferred; must be * non-negative * @throws IOException if some I/O error occurs. * @return The number of bytes, possibly zero, * that were actually transferred */ long transfer(FileChannel dst, long position, long count) throws IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/nio/FileContentEncoder.java0100644 0000000 0000000 00000004162 00000250600 025677 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import java.nio.channels.FileChannel; /** * A content encoder capable of transferring data directly from a {@link FileChannel} * * @since 4.0 */ public interface FileContentEncoder extends ContentEncoder { /** * Transfers a portion of entity content from the given file channel * to the underlying network channel. * * @param src the source FileChannel to transfer data from. * @param position * The position within the file at which the transfer is to begin; * must be non-negative * @param count * The maximum number of bytes to be transferred; must be * non-negative * @throws IOException if some I/O error occurs. * @return The number of bytes, possibly zero, * that were actually transferred */ long transfer(FileChannel src, long position, long count) throws IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/nio/HandlerFactory.java0100644 0000000 0000000 00000003564 00000250600 025077 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.protocol.HttpContext; /** * Abstract handler factory. * * @param handler type * * @since 5.0 */ public interface HandlerFactory { /** * Creates a new handler instance based on properties of * an incoming request message.. * * @param request the incoming request head. * @param context the actual execution context. * @return handler * @throws HttpException in case of an HTTP protocol violation. */ T create(HttpRequest request, HttpContext context) throws HttpException; } httpcore5/src/main/java/org/apache/hc/core5/http/nio/NHttpMessageParser.java0100644 0000000 0000000 00000004355 00000250600 025710 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.MessageHeaders; /** * Message parser intended to build HTTP message head from a session input buffer.. * * @since 4.0 */ public interface NHttpMessageParser { /** * Resets the parser. The parser will be ready to start parsing another * HTTP message. */ void reset(); /** * Attempts to parse a complete message head from the content of the * internal buffer. If the message in the input buffer is incomplete * this method will return {@code null}. * * @param buffer session input buffer. * @param endOfStream end of stream flag * @return HTTP message head, if available, {@code null} otherwise. * @throws IOException in case of an I/O error. * @throws HttpException in case the HTTP message is malformed or * violates the HTTP protocol. */ T parse(SessionInputBuffer buffer, boolean endOfStream) throws IOException, HttpException; } httpcore5/src/main/java/org/apache/hc/core5/http/nio/NHttpMessageParserFactory.java0100644 0000000 0000000 00000002666 00000250600 027243 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import org.apache.hc.core5.http.MessageHeaders; /** * Factory for {@link NHttpMessageParser} instances. * * @since 4.3 */ public interface NHttpMessageParserFactory { NHttpMessageParser create(); } httpcore5/src/main/java/org/apache/hc/core5/http/nio/NHttpMessageWriter.java0100644 0000000 0000000 00000004062 00000250600 025723 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.MessageHeaders; /** * Message writer intended to serialize HTTP message head to a session buffer. * * @param The type of {@link MessageHeaders}. * @since 4.0 */ public interface NHttpMessageWriter { /** * Resets the writer. The writer will be ready to start serializing another * HTTP message. */ void reset(); /** * Writes out the HTTP message head. * * @param message HTTP message. * @param buffer session output buffer. * @throws IOException in case of an I/O error. * @throws HttpException in case the HTTP message is malformed or * violates the HTTP protocol. */ void write(T message, SessionOutputBuffer buffer) throws IOException, HttpException; } httpcore5/src/main/java/org/apache/hc/core5/http/nio/NHttpMessageWriterFactory.java0100644 0000000 0000000 00000002666 00000250600 027263 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import org.apache.hc.core5.http.MessageHeaders; /** * Factory for {@link NHttpMessageWriter} instances. * * @since 4.3 */ public interface NHttpMessageWriterFactory { NHttpMessageWriter create(); } httpcore5/src/main/java/org/apache/hc/core5/http/nio/RequestChannel.java0100644 0000000 0000000 00000004302 00000250600 025102 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.protocol.HttpContext; /** * Abstract request channel. *

* Implementations are expected to be thread-safe. *

* @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE) public interface RequestChannel { /** * Sends a request through this channel. * * @param request the outgoing request. * @param entityDetails the details of the entity enclosed in the request * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void sendRequest(HttpRequest request, EntityDetails entityDetails, HttpContext context) throws HttpException, IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/nio/ResourceHolder.java0100644 0000000 0000000 00000003263 00000250600 025113 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; /** * Abstract resource holder. *

* Implementations are expected to ensure that {@link #releaseResources()} methods is idempotent and is * safe to invoke multiple times. *

*

* Implementations are expected to be thread-safe. *

* * @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE) public interface ResourceHolder { void releaseResources(); } httpcore5/src/main/java/org/apache/hc/core5/http/nio/ResponseChannel.java0100644 0000000 0000000 00000007076 00000250600 025263 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.protocol.HttpContext; /** * Abstract response / response promise channel. *

* Implementations are expected to be thread-safe. *

* * @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE) public interface ResponseChannel { /** * Sends an intermediate informational HTTP response through this channel. * * @param response an intermediate (1xx) HTTP response. * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void sendInformation(HttpResponse response, HttpContext context) throws HttpException, IOException; /** * Sends a final HTTP response through this channel. * * @param response a final (non 1xx) HTTP response * @param entityDetails the response entity details or {@code null} if the response * does not enclose an entity. * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void sendResponse(HttpResponse response, EntityDetails entityDetails, HttpContext context) throws HttpException, IOException; /** * Pushes a request message head through this channel as a promise to deliver * a response message. * * @param promise the request message header used as a promise. * @param responseProducer the push response message producer. * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ void pushPromise(HttpRequest promise, AsyncPushProducer responseProducer, HttpContext context) throws HttpException, IOException; /** * Terminates message exchange due to an internal error generating * a response or its content. */ @Internal default void terminateExchange() { } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/SessionInputBuffer.java0100644 0000000 0000000 00000013571 00000250600 025766 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import org.apache.hc.core5.util.CharArrayBuffer; /** * Session input buffer for HTTP/1.1 non-blocking connections. *

* This interface facilitates intermediate buffering of input data streamed from * a source channel and provides methods for reading lines of text. *

* * @since 4.0 */ public interface SessionInputBuffer { /** * Determines if the buffer contains data. * * @return {@code true} if there is data in the buffer, * {@code false} otherwise. */ boolean hasData(); /** * Returns the length of this buffer. * * @return buffer length. */ int length(); /** * Makes an attempt to fill the buffer with data from the given * {@link ReadableByteChannel}. * * @param src the source channel * @return The number of bytes read, possibly zero, or {@code -1} if the * channel has reached end-of-stream. * @throws IOException in case of an I/O error. */ int fill(ReadableByteChannel src) throws IOException; /** * Reads one byte from the buffer. If the buffer is empty this method can * throw a runtime exception. The exact type of runtime exception thrown * by this method depends on implementation. * * @return one byte */ int read(); /** * Reads a sequence of bytes from this buffer into the destination buffer, * up to the given maximum limit. The exact number of bytes transferred * depends on availability of data in this buffer and capacity of the * destination buffer, but cannot be more than {@code maxLen} value. * * @param dst the destination buffer. * @param maxLen the maximum number of bytes to be read. * @return The number of bytes read, possibly zero. */ int read(ByteBuffer dst, int maxLen); /** * Reads a sequence of bytes from this buffer into the destination buffer. * The exact number of bytes transferred depends on availability of data * in this buffer and capacity of the destination buffer. * * @param dst the destination buffer. * @return The number of bytes read, possibly zero. */ int read(ByteBuffer dst); /** * Reads a sequence of bytes from this buffer into the destination channel, * up to the given maximum limit. The exact number of bytes transferred * depends on availability of data in this buffer, but cannot be more than * {@code maxLen} value. * * @param dst the destination channel. * @param maxLen the maximum number of bytes to be read. * @return The number of bytes read, possibly zero. * @throws IOException in case of an I/O error. */ int read(WritableByteChannel dst, int maxLen) throws IOException; /** * Reads a sequence of bytes from this buffer into the destination channel. * The exact number of bytes transferred depends on availability of data in * this buffer. * * @param dst the destination channel. * @return The number of bytes read, possibly zero. * @throws IOException in case of an I/O error. */ int read(WritableByteChannel dst) throws IOException; /** * Attempts to transfer a complete line of characters up to a line delimiter * from this buffer to the destination buffer. If a complete line is * available in the buffer, the sequence of chars is transferred to the * destination buffer the method returns {@code true}. The line * delimiter itself is discarded. If a complete line is not available in * the buffer, this method returns {@code false} without transferring * anything to the destination buffer. If {@code endOfStream} parameter * is set to {@code true} this method assumes the end of stream has * been reached and the content currently stored in the buffer should be * treated as a complete line. *

* The choice of a char encoding and line delimiter sequence is up to the * specific implementations of this interface. * * @param dst the destination buffer. * @param endOfStream end of stream flag * @return {@code true} if a sequence of chars representing a complete * line has been transferred to the destination buffer, {@code false} * otherwise. * * @throws java.nio.charset.CharacterCodingException in case a character encoding or decoding * error occurs. * @throws org.apache.hc.core5.http.MessageConstraintException in case a message constraint violation. */ boolean readLine(CharArrayBuffer dst, boolean endOfStream) throws IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/nio/SessionOutputBuffer.java0100644 0000000 0000000 00000007210 00000250600 026160 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.nio.charset.CharacterCodingException; import org.apache.hc.core5.util.CharArrayBuffer; /** * Session output buffer for non-blocking HTTP/1.1 connections. *

* This interface facilitates intermediate buffering of output data streamed out * to a destination channel and provides provides methods for writing lines of text. *

* * @since 4.0 */ public interface SessionOutputBuffer { /** * Determines if the buffer contains data. * * @return {@code true} if there is data in the buffer, * {@code false} otherwise. */ boolean hasData(); /** * Returns available space in the buffer. * * @return available space. */ int capacity(); /** * Returns the length of this buffer. * * @return buffer length. */ int length(); /** * Makes an attempt to flush the content of this buffer to the given * destination {@link WritableByteChannel}. * * @param channel the destination channel. * @return The number of bytes written, possibly zero. * @throws IOException in case of an I/O error. */ int flush(WritableByteChannel channel) throws IOException; /** * Copies content of the source buffer into this buffer. The capacity of * the destination will be expanded in order to accommodate the entire * content of the source buffer. * * @param src the source buffer. */ void write(ByteBuffer src); /** * Reads a sequence of bytes from the source channel into this buffer. * * @param src the source channel. * @throws IOException in case of an I/O error. */ void write(ReadableByteChannel src) throws IOException; /** * Copies content of the source buffer into this buffer as one line of text * including a line delimiter. The capacity of the destination will be * expanded in order to accommodate the entire content of the source buffer. *

* The choice of a char encoding and line delimiter sequence is up to the * specific implementations of this interface. * * @param src the source buffer. * @throws CharacterCodingException when a character encoding error occurs. */ void writeLine(CharArrayBuffer src) throws CharacterCodingException; } httpcore5/src/main/java/org/apache/hc/core5/http/nio/StreamChannel.java0100644 0000000 0000000 00000004161 00000250600 024710 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import java.nio.Buffer; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; /** * Abstract data stream channel. *

* Implementations are expected to be thread-safe. *

* * @param data container accepted by the channel. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE) public interface StreamChannel { /** * Writes data from the data container into the underlying data stream. * * @param src source of data * @return The number of elements written, possibly zero * @throws IOException in case of an I/O error. */ int write(T src) throws IOException; /** * Terminates the underlying data stream and optionally writes * a closing sequence. * * @throws IOException in case of an I/O error. */ void endStream() throws IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/nio/package-info.java0100644 0000000 0000000 00000003726 00000250600 024516 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Core HTTP transport APIs based on the asynchronous, event driven I/O model. *

* The application programming interface is based on the concept * of channels and event handlers. The channels act as conduits * for asynchronous data output. They are generally expected to * be thread-safe and could be used by multiple threads concurrently. * The event handlers react to asynchronous signals or events and * communicate with the opposite endpoint through available channels. * Event handlers can be specialized as data producers, * data consumers or can be both. Generally event handlers can only * be used by a single thread at a time and do not require synchronization * as long as they do not interact with event handlers run by separate * threads. *

*/ package org.apache.hc.core5.http.nio; httpcore5/src/main/java/org/apache/hc/core5/http/nio/command/0040755 0000000 0000000 00000000000 00000250600 022740 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/nio/command/CommandSupport.java0100644 0000000 0000000 00000005173 00000250600 026561 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.command; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.RequestNotExecutedException; import org.apache.hc.core5.reactor.Command; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.util.Args; /** * {@link Command} utility methods. * * @since 5.0 */ @Internal public final class CommandSupport { /** * Fails all pending session {@link Command}s. */ static public void failCommands(final IOSession ioSession, final Exception ex) { Args.notNull(ioSession, "I/O session"); Command command; while ((command = ioSession.poll()) != null) { if (command instanceof ExecutableCommand) { ((ExecutableCommand) command).failed(ex); } else { command.cancel(); } } } /** * Cancels all pending session {@link Command}s. */ static public void cancelCommands(final IOSession ioSession) { Args.notNull(ioSession, "I/O session"); Command command; while ((command = ioSession.poll()) != null) { if (command instanceof ExecutableCommand) { if (!ioSession.isOpen()) { ((ExecutableCommand) command).failed(new RequestNotExecutedException()); } else { command.cancel(); } } else { command.cancel(); } } } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/command/ExecutableCommand.java0100644 0000000 0000000 00000003427 00000250600 027166 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.command; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.concurrent.CancellableDependency; import org.apache.hc.core5.reactor.Command; /** * Abstract executable command that may need to do some cleaning up * in case of an failure and also optionally may want to cancel * the associated HTTP message exchange through {@link CancellableDependency}. * * @since 5.0 */ @Internal public abstract class ExecutableCommand implements Command { public abstract CancellableDependency getCancellableDependency(); public abstract void failed(Exception ex); } httpcore5/src/main/java/org/apache/hc/core5/http/nio/command/RequestExecutionCommand.java0100644 0000000 0000000 00000010126 00000250600 030413 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.command; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.concurrent.CancellableDependency; import org.apache.hc.core5.http.RequestNotExecutedException; import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * Request execution command. * * @since 5.0 */ @Internal public final class RequestExecutionCommand extends ExecutableCommand { private final AsyncClientExchangeHandler exchangeHandler; private final HandlerFactory pushHandlerFactory; private final CancellableDependency cancellableDependency; private final HttpContext context; private final AtomicBoolean failed; public RequestExecutionCommand( final AsyncClientExchangeHandler exchangeHandler, final HandlerFactory pushHandlerFactory, final CancellableDependency cancellableDependency, final HttpContext context) { this.exchangeHandler = Args.notNull(exchangeHandler, "Handler"); this.pushHandlerFactory = pushHandlerFactory; this.cancellableDependency = cancellableDependency; this.context = context; this.failed = new AtomicBoolean(); } public RequestExecutionCommand( final AsyncClientExchangeHandler exchangeHandler, final HandlerFactory pushHandlerFactory, final HttpContext context) { this(exchangeHandler, pushHandlerFactory, null, context); } public RequestExecutionCommand( final AsyncClientExchangeHandler exchangeHandler, final HttpContext context) { this(exchangeHandler, null, null, context); } public AsyncClientExchangeHandler getExchangeHandler() { return exchangeHandler; } public HandlerFactory getPushHandlerFactory() { return pushHandlerFactory; } @Override public CancellableDependency getCancellableDependency() { return cancellableDependency; } public HttpContext getContext() { return context; } @Override public void failed(final Exception ex) { if (failed.compareAndSet(false, true)) { try { exchangeHandler.failed(ex); } finally { exchangeHandler.releaseResources(); } } } @Override public boolean cancel() { if (failed.compareAndSet(false, true)) { try { exchangeHandler.failed(new RequestNotExecutedException()); return true; } finally { exchangeHandler.releaseResources(); } } return false; } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/command/ShutdownCommand.java0100644 0000000 0000000 00000005077 00000250600 026723 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.command; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.Command; import org.apache.hc.core5.reactor.IOSession; /** * Shutdown command. Two shutdown modes are supported: {@link CloseMode#GRACEFUL} and * {@link CloseMode#IMMEDIATE}. The exact implementation of both modes is protocol or handler * specific. * * @since 5.0 */ public final class ShutdownCommand implements Command { public static final ShutdownCommand GRACEFUL = new ShutdownCommand(CloseMode.GRACEFUL); public static final ShutdownCommand IMMEDIATE = new ShutdownCommand(CloseMode.IMMEDIATE); public static final Callback GRACEFUL_IMMEDIATE_CALLBACK = createIOSessionCallback(Priority.IMMEDIATE); public static final Callback GRACEFUL_NORMAL_CALLBACK = createIOSessionCallback(Priority.NORMAL); private static Callback createIOSessionCallback(final Priority priority) { return session -> session.enqueue(ShutdownCommand.GRACEFUL, priority); } private final CloseMode type; public ShutdownCommand(final CloseMode type) { this.type = type; } public CloseMode getType() { return type; } @Override public boolean cancel() { return true; } @Override public String toString() { return "Shutdown: " + type; } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/command/StaleCheckCommand.java0100644 0000000 0000000 00000004236 00000250600 027112 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.command; import java.util.function.Consumer; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.reactor.Command; import org.apache.hc.core5.util.Args; /** * Stale check command. The {@code callback} will be invoked after the client's event loop has finished processing * all pending reads on the connection. If the connection is still active at that point, the callback will be completed * with {@code true}. In any other event, the callback will be cancelled, failed, or completed with {@code false}. * * @since 5.4 */ @Internal public final class StaleCheckCommand implements Command { private final Consumer callback; public StaleCheckCommand(final Consumer callback) { this.callback = Args.notNull(callback, "Callback"); } public Consumer getCallback() { return callback; } @Override public boolean cancel() { callback.accept(false); return true; } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/command/package-info.java0100644 0000000 0000000 00000002446 00000250600 026132 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Commands for HTTP transports based on asynchronous, event driven I/O model. */ package org.apache.hc.core5.http.nio.command; httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/0040755 0000000 0000000 00000000000 00000250600 022636 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractBinAsyncEntityConsumer.java0100644 0000000 0000000 00000007127 00000250600 031610 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.charset.UnsupportedCharsetException; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.nio.AsyncEntityConsumer; import org.apache.hc.core5.util.Args; /** * Abstract binary entity content consumer. * * @since 5.0 * * @param entity representation. */ public abstract class AbstractBinAsyncEntityConsumer extends AbstractBinDataConsumer implements AsyncEntityConsumer { private volatile FutureCallback resultCallback; private volatile T content; /** * Triggered to signal beginning of entity content stream. * * @param contentType the entity content type * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ protected abstract void streamStart(ContentType contentType) throws HttpException, IOException; /** * Triggered to generate entity representation. * * @return the entity content * @throws IOException in case of an I/O error. */ protected abstract T generateContent() throws IOException; @Override public final void streamStart( final EntityDetails entityDetails, final FutureCallback resultCallback) throws IOException, HttpException { Args.notNull(resultCallback, "Result callback"); this.resultCallback = resultCallback; try { final ContentType contentType = entityDetails != null ? ContentType.parse(entityDetails.getContentType()) : null; streamStart(contentType); } catch (final UnsupportedCharsetException ex) { throw new UnsupportedEncodingException(ex.getMessage()); } } @Override protected final void completed() throws IOException { content = generateContent(); if (resultCallback != null) { resultCallback.completed(content); } releaseResources(); } @Override public final void failed(final Exception cause) { if (resultCallback != null) { resultCallback.failed(cause); } releaseResources(); } @Override public final T getContent() { return content; } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractBinAsyncEntityProducer.java0100644 0000000 0000000 00000016217 00000250600 031600 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Collections; import java.util.Objects; import java.util.Set; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.StreamChannel; import org.apache.hc.core5.util.Args; /** * Abstract binary entity content producer. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL) public abstract class AbstractBinAsyncEntityProducer implements AsyncEntityProducer { enum State { ACTIVE, FLUSHING, END_STREAM } private final int fragmentSizeHint; private final ByteBuffer byteBuffer; private final ContentType contentType; private final ReentrantLock lock; private volatile State state; public AbstractBinAsyncEntityProducer(final int fragmentSizeHint, final ContentType contentType) { this.fragmentSizeHint = fragmentSizeHint >= 0 ? fragmentSizeHint : 0; this.byteBuffer = ByteBuffer.allocate(this.fragmentSizeHint); this.contentType = contentType; this.state = State.ACTIVE; this.lock = new ReentrantLock(); } private void flush(final StreamChannel channel) throws IOException { if (byteBuffer.position() > 0) { byteBuffer.flip(); channel.write(byteBuffer); byteBuffer.compact(); } } final int writeData(final StreamChannel channel, final ByteBuffer src) throws IOException { final int chunk = src.remaining(); if (chunk == 0) { return 0; } if (chunk > fragmentSizeHint) { // the data chunk is greater than the fragment hint // attempt to write it out to the channel directly // flush the buffer if not empty flush(channel); if (byteBuffer.position() == 0) { return channel.write(src); } } else { // the data chunk is smaller than the fragment hint // attempt to buffer it // flush the buffer if there is not enough space to store the chunk if (byteBuffer.remaining() < chunk) { flush(channel); } if (byteBuffer.remaining() >= chunk) { byteBuffer.put(src); if (!byteBuffer.hasRemaining()) { flush(channel); } return chunk; } } return 0; } final void streamEnd(final StreamChannel channel) throws IOException { if (state == State.ACTIVE) { state = State.FLUSHING; flush(channel); if (byteBuffer.position() == 0) { state = State.END_STREAM; channel.endStream(); } } } /** * Returns the number of bytes immediately available for output. * This method can be used as a hint to control output events * of the underlying I/O session. * * @return the number of bytes immediately available for output */ protected abstract int availableData(); /** * Triggered to signal the ability of the underlying byte channel * to accept more data. The data producer can choose to write data * immediately inside the call or asynchronously at some later point. *

* {@link StreamChannel} passed to this method is threading-safe. * * @param channel the data channel capable to accepting more data. * @throws IOException in case of an I/O error. */ protected abstract void produceData(StreamChannel channel) throws IOException; @Override public final String getContentType() { return Objects.toString(contentType, null); } @Override public String getContentEncoding() { return null; } @Override public boolean isChunked() { return false; } @Override public Set getTrailerNames() { return Collections.emptySet(); } @Override public long getContentLength() { return -1; } @Override public final int available() { if (state == State.ACTIVE) { return availableData(); } lock.lock(); try { return byteBuffer.position(); } finally { lock.unlock(); } } @Override public final void produce(final DataStreamChannel channel) throws IOException { lock.lock(); try { if (state == State.ACTIVE) { produceData(new StreamChannel() { @Override public int write(final ByteBuffer src) throws IOException { Args.notNull(src, "Buffer"); lock.lock(); try { return writeData(channel, src); } finally { lock.unlock(); } } @Override public void endStream() throws IOException { lock.lock(); try { streamEnd(channel); } finally { lock.unlock(); } } }); } if (state == State.FLUSHING) { flush(channel); if (byteBuffer.position() == 0) { state = State.END_STREAM; channel.endStream(); } } } finally { lock.unlock(); } } @Override public void releaseResources() { state = State.ACTIVE; } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractBinDataConsumer.java0100644 0000000 0000000 00000005600 00000250600 030201 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.nio.AsyncDataConsumer; import org.apache.hc.core5.http.nio.CapacityChannel; /** * Abstract binary data consumer. * * @since 5.0 */ public abstract class AbstractBinDataConsumer implements AsyncDataConsumer { private static final ByteBuffer EMPTY = ByteBuffer.wrap(new byte[0]); /** * Triggered to obtain the capacity increment. * * @return the number of bytes this consumer is prepared to process. */ protected abstract int capacityIncrement(); /** * Triggered to pass incoming data packet to the data consumer. * * @param src the data packet. * @param endOfStream flag indicating whether this data packet is the last in the data stream. * @throws IOException in case of an I/O error. */ protected abstract void data(ByteBuffer src, boolean endOfStream) throws IOException; /** * Triggered to signal completion of data processing. * * @throws IOException in case of an I/O error. */ protected abstract void completed() throws IOException; @Override public final void updateCapacity(final CapacityChannel capacityChannel) throws IOException { capacityChannel.update(capacityIncrement()); } @Override public final void consume(final ByteBuffer src) throws IOException { data(src, false); } @Override public final void streamEnd(final List trailers) throws HttpException, IOException { data(EMPTY, true); completed(); } }httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractCharAsyncEntityConsumer.java0100644 0000000 0000000 00000007646 00000250600 031763 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.charset.UnsupportedCharsetException; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.nio.AsyncEntityConsumer; import org.apache.hc.core5.util.Args; /** * Abstract text entity content consumer. * * @since 5.0 * * @param entity representation. */ public abstract class AbstractCharAsyncEntityConsumer extends AbstractCharDataConsumer implements AsyncEntityConsumer { private volatile FutureCallback resultCallback; private volatile T content; protected AbstractCharAsyncEntityConsumer(final int bufSize, final CharCodingConfig charCodingConfig) { super(bufSize, charCodingConfig); } public AbstractCharAsyncEntityConsumer() { } /** * Triggered to signal beginning of entity content stream. * * @param contentType the entity content type * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ protected abstract void streamStart(ContentType contentType) throws HttpException, IOException; /** * Triggered to generate entity representation. * * @return the entity content * @throws IOException in case of an I/O error. */ protected abstract T generateContent() throws IOException; @Override public final void streamStart( final EntityDetails entityDetails, final FutureCallback resultCallback) throws IOException, HttpException { Args.notNull(resultCallback, "Result callback"); this.resultCallback = resultCallback; try { final ContentType contentType = entityDetails != null ? ContentType.parse(entityDetails.getContentType()) : null; setCharset(ContentType.getCharset(contentType, null)); streamStart(contentType); } catch (final UnsupportedCharsetException ex) { throw new UnsupportedEncodingException(ex.getMessage()); } } @Override protected final void completed() throws IOException { content = generateContent(); if (resultCallback != null) { resultCallback.completed(content); } releaseResources(); } @Override public final void failed(final Exception cause) { if (resultCallback != null) { resultCallback.failed(cause); } releaseResources(); } @Override public final T getContent() { return content; } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractCharAsyncEntityProducer.java0100644 0000000 0000000 00000017642 00000250600 031750 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.Set; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.StreamChannel; import org.apache.hc.core5.util.Args; /** * Abstract text entity content producer. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL) public abstract class AbstractCharAsyncEntityProducer implements AsyncEntityProducer { private static final CharBuffer EMPTY = CharBuffer.wrap(new char[0]); enum State { ACTIVE, FLUSHING, END_STREAM } private final ByteBuffer bytebuf; private final int fragmentSizeHint; private final ContentType contentType; private final CharsetEncoder charsetEncoder; private final ReentrantLock lock; private volatile State state; public AbstractCharAsyncEntityProducer( final int bufferSize, final int fragmentSizeHint, final ContentType contentType) { Args.positive(bufferSize, "Buffer size"); this.fragmentSizeHint = fragmentSizeHint >= 0 ? fragmentSizeHint : 0; this.bytebuf = ByteBuffer.allocate(bufferSize); this.contentType = contentType; final Charset charset = ContentType.getCharset(contentType, StandardCharsets.UTF_8); this.charsetEncoder = charset.newEncoder(); this.state = State.ACTIVE; this.lock = new ReentrantLock(); } private void flush(final StreamChannel channel) throws IOException { if (bytebuf.position() > 0) { bytebuf.flip(); channel.write(bytebuf); bytebuf.compact(); } } final int writeData(final StreamChannel channel, final CharBuffer src) throws IOException { final int chunk = src.remaining(); if (chunk == 0) { return 0; } final int p = src.position(); final CoderResult result = charsetEncoder.encode(src, bytebuf, false); if (result.isError()) { result.throwException(); } if (!bytebuf.hasRemaining() || bytebuf.position() >= fragmentSizeHint) { flush(channel); } return src.position() - p; } final void streamEnd(final StreamChannel channel) throws IOException { if (state == State.ACTIVE) { state = State.FLUSHING; if (!bytebuf.hasRemaining()) { flush(channel); } final CoderResult result = charsetEncoder.encode(EMPTY, bytebuf, true); if (result.isError()) { result.throwException(); } final CoderResult result2 = charsetEncoder.flush(bytebuf); if (result2.isError()) { result.throwException(); } else if (result.isUnderflow()) { flush(channel); if (bytebuf.position() == 0) { state = State.END_STREAM; channel.endStream(); } } } } /** * Returns the number of bytes immediately available for output. * This method can be used as a hint to control output events * of the underlying I/O session. * * @return the number of bytes immediately available for output */ protected abstract int availableData(); /** * Triggered to signal the ability of the underlying char channel * to accept more data. The data producer can choose to write data * immediately inside the call or asynchronously at some later point. *

* {@link StreamChannel} passed to this method is threading-safe. * * @param channel the data channel capable to accepting more data. * @throws IOException in case of an I/O error. */ protected abstract void produceData(StreamChannel channel) throws IOException; @Override public final String getContentType() { return Objects.toString(contentType, null); } @Override public String getContentEncoding() { return null; } @Override public long getContentLength() { return -1; } @Override public boolean isChunked() { return false; } @Override public Set getTrailerNames() { return null; } @Override public final int available() { if (state == State.ACTIVE) { return availableData(); } lock.lock(); try { return bytebuf.position(); } finally { lock.unlock(); } } @Override public final void produce(final DataStreamChannel channel) throws IOException { lock.lock(); try { if (state == State.ACTIVE) { produceData(new StreamChannel() { @Override public int write(final CharBuffer src) throws IOException { Args.notNull(src, "Buffer"); lock.lock(); try { return writeData(channel, src); } finally { lock.unlock(); } } @Override public void endStream() throws IOException { lock.lock(); try { streamEnd(channel); } finally { lock.unlock(); } } }); } if (state == State.FLUSHING) { final CoderResult result = charsetEncoder.flush(bytebuf); if (result.isError()) { result.throwException(); } else if (result.isOverflow()) { flush(channel); } else if (result.isUnderflow()) { flush(channel); if (bytebuf.position() == 0) { state = State.END_STREAM; channel.endStream(); } } } } finally { lock.unlock(); } } @Override public void releaseResources() { state = State.ACTIVE; charsetEncoder.reset(); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractCharDataConsumer.java0100644 0000000 0000000 00000015730 00000250600 030353 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; import java.nio.charset.StandardCharsets; import java.util.List; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.nio.AsyncDataConsumer; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.util.Args; /** * Abstract text data consumer. * * @since 5.0 */ public abstract class AbstractCharDataConsumer implements AsyncDataConsumer { protected static final int DEF_BUF_SIZE = 8192; private static final ByteBuffer EMPTY_BIN = ByteBuffer.wrap(new byte[0]); private final CharBuffer charBuffer; private final CharCodingConfig charCodingConfig; private volatile Charset charset; private volatile CharsetDecoder charsetDecoder; private volatile ByteBuffer byteBuffer; protected AbstractCharDataConsumer(final int bufSize, final CharCodingConfig charCodingConfig) { this.charBuffer = CharBuffer.allocate(Args.positive(bufSize, "Buffer size")); this.charCodingConfig = charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT; } public AbstractCharDataConsumer() { this(DEF_BUF_SIZE, CharCodingConfig.DEFAULT); } /** * Triggered to obtain the capacity increment. * * @return the number of bytes this consumer is prepared to process. */ protected abstract int capacityIncrement(); /** * Triggered to pass incoming data packet to the data consumer. * * @param src the data packet. * @param endOfStream flag indicating whether this data packet is the last in the data stream. * @throws IOException in case of an I/O error. */ protected abstract void data(CharBuffer src, boolean endOfStream) throws IOException; /** * Triggered to signal completion of data processing. * * @throws IOException in case of an I/O error. */ protected abstract void completed() throws IOException; protected final void setCharset(final Charset charset) { this.charset = charset != null ? charset : charCodingConfig.getCharset(); this.charsetDecoder = null; } @Override public final void updateCapacity(final CapacityChannel capacityChannel) throws IOException { capacityChannel.update(capacityIncrement()); } private void checkResult(final CoderResult result) throws IOException { if (result.isError()) { result.throwException(); } } private void doDecode(final boolean endOfStream) throws IOException { charBuffer.flip(); data(charBuffer, endOfStream); charBuffer.clear(); } private CharsetDecoder getCharsetDecoder() { CharsetDecoder charsetDecoder = this.charsetDecoder; if (charsetDecoder == null) { Charset charset = this.charset; if (charset == null) { charset = charCodingConfig.getCharset(); } if (charset == null) { charset = StandardCharsets.UTF_8; } charsetDecoder = charset.newDecoder(); this.charsetDecoder = charsetDecoder; if (charCodingConfig.getMalformedInputAction() != null) { charsetDecoder.onMalformedInput(charCodingConfig.getMalformedInputAction()); } if (charCodingConfig.getUnmappableInputAction() != null) { charsetDecoder.onUnmappableCharacter(charCodingConfig.getUnmappableInputAction()); } } return charsetDecoder; } @Override public final void consume(final ByteBuffer src) throws IOException { final CharsetDecoder charsetDecoder = getCharsetDecoder(); while (src.hasRemaining()) { ByteBuffer byteBuffer = this.byteBuffer; if (byteBuffer != null && byteBuffer.position() > 0) { // There are some left-overs from the previous input operation final int n = byteBuffer.remaining(); if (n < src.remaining()) { final int oldLimit = src.limit(); src.limit(src.position() + n); byteBuffer.put(src); src.limit(oldLimit); } else { byteBuffer.put(src); } byteBuffer.flip(); final CoderResult r = charsetDecoder.decode(byteBuffer, charBuffer, false); checkResult(r); doDecode(false); byteBuffer.compact(); } if (byteBuffer == null || byteBuffer.position() == 0) { final CoderResult r = charsetDecoder.decode(src, charBuffer, false); checkResult(r); doDecode(false); if (r.isUnderflow() && src.hasRemaining()) { // in case of input underflow src can be expected to be very small (one incomplete UTF8 char) if (byteBuffer == null) { byteBuffer = ByteBuffer.allocate(Math.max(src.remaining(), 1024)); this.byteBuffer = byteBuffer; } byteBuffer.put(src); } } } } @Override public final void streamEnd(final List trailers) throws HttpException, IOException { final CharsetDecoder charsetDecoder = getCharsetDecoder(); checkResult(charsetDecoder.decode(EMPTY_BIN, charBuffer, true)); doDecode(false); checkResult(charsetDecoder.flush(charBuffer)); doDecode(true); completed(); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AsyncEntityProducerWrapper.java0100644 0000000 0000000 00000006615 00000250600 031025 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.util.Set; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.util.Args; /** * Base class for wrapping entity producers that delegates all calls to the wrapped producer. * Implementations can derive from this class and override only those methods that * should not be delegated to the wrapped producer. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public class AsyncEntityProducerWrapper implements AsyncEntityProducer { private final AsyncEntityProducer wrappedEntityProducer; public AsyncEntityProducerWrapper(final AsyncEntityProducer wrappedEntityProducer) { super(); this.wrappedEntityProducer = Args.notNull(wrappedEntityProducer, "Wrapped entity producer"); } @Override public boolean isRepeatable() { return wrappedEntityProducer.isRepeatable(); } @Override public boolean isChunked() { return wrappedEntityProducer.isChunked(); } @Override public long getContentLength() { return wrappedEntityProducer.getContentLength(); } @Override public String getContentType() { return wrappedEntityProducer.getContentType(); } @Override public String getContentEncoding() { return wrappedEntityProducer.getContentEncoding(); } @Override public Set getTrailerNames() { return wrappedEntityProducer.getTrailerNames(); } @Override public int available() { return wrappedEntityProducer.available(); } @Override public void produce(final DataStreamChannel channel) throws IOException { wrappedEntityProducer.produce(channel); } @Override public void failed(final Exception cause) { wrappedEntityProducer.failed(cause); } @Override public void releaseResources() { wrappedEntityProducer.releaseResources(); } @Override public String toString() { return "Wrapper [" + wrappedEntityProducer + "]"; } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AsyncEntityProducers.java0100644 0000000 0000000 00000021713 00000250600 027643 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.StreamChannel; import org.apache.hc.core5.net.WWWFormCodec; /** * {AsyncEntityProducer} factory methods. * * @since 5.0 */ public final class AsyncEntityProducers { private AsyncEntityProducers() { } public static AsyncEntityProducer create(final String content, final ContentType contentType) { return new BasicAsyncEntityProducer(content, contentType); } public static AsyncEntityProducer create(final String content, final Charset charset) { return new BasicAsyncEntityProducer(content, ContentType.TEXT_PLAIN.withCharset(charset)); } public static AsyncEntityProducer create(final String content) { return new BasicAsyncEntityProducer(content, ContentType.TEXT_PLAIN); } public static AsyncEntityProducer create(final byte[] content, final ContentType contentType) { return new BasicAsyncEntityProducer(content, contentType); } public static AsyncEntityProducer create(final File content, final ContentType contentType) { return new FileEntityProducer(content, contentType); } public static AsyncEntityProducer createUrlEncoded( final Iterable parameters, final Charset charset) { final ContentType contentType = charset != null ? ContentType.APPLICATION_FORM_URLENCODED.withCharset(charset) : ContentType.APPLICATION_FORM_URLENCODED; return create(WWWFormCodec.format(parameters, contentType.getCharset()), contentType); } public static AsyncEntityProducer createBinary( final Callback> callback, final ContentType contentType) { return new AbstractBinAsyncEntityProducer(0, contentType) { @Override protected int availableData() { return Integer.MAX_VALUE; } @Override protected void produceData(final StreamChannel channel) throws IOException { callback.execute(channel); } @Override public boolean isRepeatable() { return false; } @Override public void failed(final Exception cause) { } }; } public static AsyncEntityProducer createText( final Callback> callback, final ContentType contentType) { return new AbstractCharAsyncEntityProducer(4096, 2048, contentType) { @Override protected int availableData() { return Integer.MAX_VALUE; } @Override protected void produceData(final StreamChannel channel) throws IOException { callback.execute(channel); } @Override public boolean isRepeatable() { return false; } @Override public void failed(final Exception cause) { } }; } public static AsyncEntityProducer withTrailers(final AsyncEntityProducer entity, final Header... trailers) { return new AsyncEntityProducerWrapper(entity) { @Override public boolean isChunked() { // Must be chunk coded return true; } @Override public long getContentLength() { return -1; } @Override public Set getTrailerNames() { final Set names = new LinkedHashSet<>(); for (final Header trailer: trailers) { names.add(trailer.getName()); } return names; } @Override public void produce(final DataStreamChannel channel) throws IOException { super.produce(new DataStreamChannel() { @Override public void requestOutput() { channel.requestOutput(); } @Override public int write(final ByteBuffer src) throws IOException { return channel.write(src); } @Override public void endStream(final List p) throws IOException { final List

allTrailers; if (p != null && !p.isEmpty()) { allTrailers = new ArrayList<>(p); allTrailers.addAll(Arrays.asList(trailers)); } else { allTrailers = Arrays.asList(trailers); } channel.endStream(allTrailers); } @Override public void endStream() throws IOException { channel.endStream(); } }); } }; } public static AsyncEntityProducer create(final String content, final ContentType contentType, final Header... trailers) { return withTrailers(create(content, contentType), trailers); } public static AsyncEntityProducer create(final String content, final Charset charset, final Header... trailers) { return withTrailers(create(content, charset), trailers); } public static AsyncEntityProducer create(final String content, final Header... trailers) { return withTrailers(create(content), trailers); } public static AsyncEntityProducer create(final byte[] content, final ContentType contentType, final Header... trailers) { return withTrailers(create(content, contentType), trailers); } public static AsyncEntityProducer create(final File content, final ContentType contentType, final Header... trailers) { return withTrailers(create(content, contentType), trailers); } /** * @throws IOException in case of an I/O error. * @since 5.2 */ public static AsyncEntityProducer create(final Path content, final ContentType contentType, final Header... trailers) throws IOException { return withTrailers(new PathEntityProducer(content, contentType, StandardOpenOption.READ), trailers); } /** * @throws IOException in case of an I/O error. * @since 5.2 */ public static AsyncEntityProducer create(final Path content, final ContentType contentType, final OpenOption... options) throws IOException { return new PathEntityProducer(content, contentType, options); } public static AsyncEntityProducer createBinary( final Callback> callback, final ContentType contentType, final Header... trailers) { return withTrailers(createBinary(callback, contentType), trailers); } public static AsyncEntityProducer createText( final Callback> callback, final ContentType contentType, final Header... trailers) { return withTrailers(createText(callback, contentType), trailers); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/BasicAsyncEntityConsumer.java0100644 0000000 0000000 00000004540 00000250600 030431 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.nio.ByteBuffer; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.util.ByteArrayBuffer; /** * Basic {@link org.apache.hc.core5.http.nio.AsyncEntityConsumer} implementation * that processes the data stream content into a byte array. * * @since 5.0 */ public class BasicAsyncEntityConsumer extends AbstractBinAsyncEntityConsumer { private final ByteArrayBuffer buffer; public BasicAsyncEntityConsumer() { super(); this.buffer = new ByteArrayBuffer(1024); } @Override protected void streamStart(final ContentType contentType) throws HttpException, IOException { } @Override protected int capacityIncrement() { return Integer.MAX_VALUE; } @Override protected void data(final ByteBuffer src, final boolean endOfStream) throws IOException { buffer.append(src); } @Override protected byte[] generateContent() throws IOException { return buffer.toByteArray(); } @Override public void releaseResources() { buffer.clear(); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/BasicAsyncEntityProducer.java0100644 0000000 0000000 00000011716 00000250600 030424 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.util.Args; /** * Basic {@link AsyncEntityProducer} implementation that generates data stream * from content of a byte array. * * @since 5.0 */ public class BasicAsyncEntityProducer implements AsyncEntityProducer { private final ByteBuffer bytebuf; private final int length; private final ContentType contentType; private final boolean chunked; private final AtomicBoolean endOfStream; private final AtomicReference exception; public BasicAsyncEntityProducer(final byte[] content, final ContentType contentType, final boolean chunked) { Args.notNull(content, "Content"); this.bytebuf = ByteBuffer.wrap(content); this.length = this.bytebuf.remaining(); this.contentType = contentType; this.chunked = chunked; this.endOfStream = new AtomicBoolean(); this.exception = new AtomicReference<>(); } public BasicAsyncEntityProducer(final byte[] content, final ContentType contentType) { this(content, contentType, false); } public BasicAsyncEntityProducer(final byte[] content) { this(content, ContentType.APPLICATION_OCTET_STREAM); } public BasicAsyncEntityProducer(final CharSequence content, final ContentType contentType, final boolean chunked) { Args.notNull(content, "Content"); this.contentType = contentType; final Charset charset = ContentType.getCharset(contentType, StandardCharsets.UTF_8); this.bytebuf = charset.encode(CharBuffer.wrap(content)); this.length = this.bytebuf.remaining(); this.chunked = chunked; this.endOfStream = new AtomicBoolean(); this.exception = new AtomicReference<>(); } public BasicAsyncEntityProducer(final CharSequence content, final ContentType contentType) { this(content, contentType, false); } public BasicAsyncEntityProducer(final CharSequence content) { this(content, ContentType.TEXT_PLAIN); } @Override public boolean isRepeatable() { return true; } @Override public final String getContentType() { return Objects.toString(contentType, null); } @Override public long getContentLength() { return length; } @Override public int available() { return Integer.MAX_VALUE; } @Override public String getContentEncoding() { return null; } @Override public boolean isChunked() { return chunked; } @Override public Set getTrailerNames() { return Collections.emptySet(); } @Override public final void produce(final DataStreamChannel channel) throws IOException { if (bytebuf.hasRemaining()) { channel.write(bytebuf); } if (!bytebuf.hasRemaining() && endOfStream.compareAndSet(false, true)) { channel.endStream(); } } @Override public final void failed(final Exception cause) { if (exception.compareAndSet(null, cause)) { releaseResources(); } } public final Exception getException() { return exception.get(); } @Override public void releaseResources() { bytebuf.clear(); bytebuf.limit(length); endOfStream.set(false); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/DigestingEntityConsumer.java0100644 0000000 0000000 00000010027 00000250600 030324 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.nio.AsyncEntityConsumer; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.util.Args; /** * {@link AsyncEntityConsumer} decorator that calculates a digest hash from * the data stream content and keeps the list of trailers received with * the data stream. * * @param entity representation. * @since 5.0 */ public class DigestingEntityConsumer implements AsyncEntityConsumer { private final AsyncEntityConsumer wrapped; private final List
trailers; private final MessageDigest digester; private volatile byte[] digest; public DigestingEntityConsumer( final String algo, final AsyncEntityConsumer wrapped) throws NoSuchAlgorithmException { this.wrapped = Args.notNull(wrapped, "Entity consumer"); this.trailers = new ArrayList<>(); this.digester = MessageDigest.getInstance(algo); } @Override public void streamStart( final EntityDetails entityDetails, final FutureCallback resultCallback) throws IOException, HttpException { wrapped.streamStart(entityDetails, resultCallback); } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { wrapped.updateCapacity(capacityChannel); } @Override public void consume(final ByteBuffer src) throws IOException { src.mark(); digester.update(src); src.reset(); wrapped.consume(src); } @Override public void streamEnd(final List trailers) throws HttpException, IOException { if (trailers != null) { this.trailers.addAll(trailers); } digest = digester.digest(); wrapped.streamEnd(trailers); } @Override public void failed(final Exception cause) { wrapped.failed(cause); } @Override public T getContent() { return wrapped.getContent(); } @Override public void releaseResources() { wrapped.releaseResources(); } /** * List of trailers sent with the data stream. * * @return the list of trailers sent with the data stream */ public List
getTrailers() { return trailers != null ? new ArrayList<>(trailers) : null; } /** * Returns digest hash. * * @return the digest hash value. */ public byte[] getDigest() { return digest; } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/DigestingEntityProducer.java0100644 0000000 0000000 00000012175 00000250600 030322 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TextUtils; /** * {@link AsyncEntityProducer} decorator that calculates a digest hash from * the data stream content and appends its value to the list of trailers. * * @since 5.0 */ public class DigestingEntityProducer implements AsyncEntityProducer { private final AsyncEntityProducer wrapped; private final MessageDigest digester; private volatile byte[] digest; public DigestingEntityProducer( final String algo, final AsyncEntityProducer wrapped) { this.wrapped = Args.notNull(wrapped, "Entity consumer"); try { this.digester = MessageDigest.getInstance(algo); } catch (final NoSuchAlgorithmException ex) { throw new IllegalArgumentException("Unsupported digest algorithm: " + algo); } } @Override public boolean isRepeatable() { return wrapped.isRepeatable(); } @Override public long getContentLength() { return wrapped.getContentLength(); } @Override public String getContentType() { return wrapped.getContentType(); } @Override public String getContentEncoding() { return wrapped.getContentEncoding(); } @Override public boolean isChunked() { return wrapped.isChunked(); } @Override public Set getTrailerNames() { final Set allNames = new LinkedHashSet<>(); final Set names = wrapped.getTrailerNames(); if (names != null) { allNames.addAll(names); } allNames.add("digest-algo"); allNames.add("digest"); return allNames; } @Override public int available() { return wrapped.available(); } @Override public void produce(final DataStreamChannel channel) throws IOException { wrapped.produce(new DataStreamChannel() { @Override public void requestOutput() { channel.requestOutput(); } @Override public int write(final ByteBuffer src) throws IOException { final ByteBuffer dup = src.duplicate(); final int writtenBytes = channel.write(src); if (writtenBytes > 0) { dup.limit(dup.position() + writtenBytes); digester.update(dup); } return writtenBytes; } @Override public void endStream(final List trailers) throws IOException { digest = digester.digest(); final List
allTrailers = new ArrayList<>(); if (trailers != null) { allTrailers.addAll(trailers); } allTrailers.add(new BasicHeader("digest-algo", digester.getAlgorithm())); allTrailers.add(new BasicHeader("digest", TextUtils.toHexString(digest))); channel.endStream(allTrailers); } @Override public void endStream() throws IOException { endStream(null); } }); } @Override public void failed(final Exception cause) { wrapped.failed(cause); } @Override public void releaseResources() { wrapped.releaseResources(); } /** * Returns digest hash. * * @return the digest hash value. */ public byte[] getDigest() { return digest; } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/DiscardingEntityConsumer.java0100644 0000000 0000000 00000005457 00000250600 030471 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.nio.AsyncEntityConsumer; import org.apache.hc.core5.http.nio.CapacityChannel; /** * No-op {@link AsyncEntityConsumer} that discards all data from the data stream. * * @param entity representation. * @since 5.2 */ public final class DiscardingEntityConsumer implements AsyncEntityConsumer { private volatile FutureCallback resultCallback; @Override public void streamStart( final EntityDetails entityDetails, final FutureCallback resultCallback) throws IOException, HttpException { this.resultCallback = resultCallback; } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { capacityChannel.update(Integer.MAX_VALUE); } @Override public void consume(final ByteBuffer src) throws IOException { } @Override public void streamEnd(final List trailers) throws IOException { if (resultCallback != null) { resultCallback.completed(null); } } @Override public void failed(final Exception cause) { if (resultCallback != null) { resultCallback.failed(cause); } } @Override public T getContent() { return null; } @Override public void releaseResources() { } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/FileEntityProducer.java0100644 0000000 0000000 00000011570 00000250600 027262 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.util.Collections; import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.io.Closer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; /** * {@link AsyncEntityProducer} implementation that generates data stream * from content of a {@link File}. * * @since 5.0 */ public final class FileEntityProducer implements AsyncEntityProducer { private final File file; private final ByteBuffer byteBuffer; private final long length; private final ContentType contentType; private final boolean chunked; private final AtomicReference exception; private final AtomicReference accessFileRef; private boolean eof; public FileEntityProducer(final File file, final int bufferSize, final ContentType contentType, final boolean chunked) { this.file = Args.notNull(file, "File"); this.length = file.length(); this.byteBuffer = ByteBuffer.allocate(bufferSize); this.contentType = contentType; this.chunked = chunked; this.accessFileRef = new AtomicReference<>(); this.exception = new AtomicReference<>(); } public FileEntityProducer(final File file, final ContentType contentType, final boolean chunked) { this(file, 8192, contentType, chunked); } public FileEntityProducer(final File file, final ContentType contentType) { this(file, contentType, false); } public FileEntityProducer(final File file) { this(file, ContentType.APPLICATION_OCTET_STREAM); } @Override public boolean isRepeatable() { return true; } @Override public String getContentType() { return Objects.toString(contentType, null); } @Override public long getContentLength() { return length; } @Override public int available() { return Integer.MAX_VALUE; } @Override public String getContentEncoding() { return null; } @Override public boolean isChunked() { return chunked; } @Override public Set getTrailerNames() { return Collections.emptySet(); } @Override public void produce(final DataStreamChannel channel) throws IOException { @SuppressWarnings("resource") RandomAccessFile accessFile = accessFileRef.get(); if (accessFile == null) { accessFile = new RandomAccessFile(file, "r"); Asserts.check(accessFileRef.getAndSet(accessFile) == null, "Illegal producer state"); } if (!eof) { final int bytesRead = accessFile.getChannel().read(byteBuffer); if (bytesRead < 0) { eof = true; } } if (byteBuffer.position() > 0) { byteBuffer.flip(); channel.write(byteBuffer); byteBuffer.compact(); } if (eof && byteBuffer.position() == 0) { channel.endStream(); releaseResources(); } } @Override public void failed(final Exception cause) { if (exception.compareAndSet(null, cause)) { releaseResources(); } } public Exception getException() { return exception.get(); } @Override public void releaseResources() { eof = false; Closer.closeQuietly(accessFileRef.getAndSet(null)); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/NoopEntityConsumer.java0100644 0000000 0000000 00000005520 00000250600 027324 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.nio.AsyncEntityConsumer; import org.apache.hc.core5.http.nio.CapacityChannel; /** * No-op {@link AsyncEntityConsumer} that discards all data from the data stream. * * @since 5.0 * * @deprecated Use {@link DiscardingEntityConsumer} */ @Deprecated public final class NoopEntityConsumer implements AsyncEntityConsumer { private volatile FutureCallback resultCallback; @Override public void streamStart( final EntityDetails entityDetails, final FutureCallback resultCallback) throws IOException, HttpException { this.resultCallback = resultCallback; } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { capacityChannel.update(Integer.MAX_VALUE); } @Override public void consume(final ByteBuffer src) throws IOException { } @Override public void streamEnd(final List trailers) throws IOException { if (resultCallback != null) { resultCallback.completed(null); } } @Override public void failed(final Exception cause) { if (resultCallback != null) { resultCallback.failed(cause); } } @Override public Void getContent() { return null; } @Override public void releaseResources() { } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/PathEntityProducer.java0100644 0000000 0000000 00000012614 00000250600 027277 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SeekableByteChannel; import java.nio.file.Files; import java.nio.file.OpenOption; import java.nio.file.Path; import java.util.Collections; import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.io.Closer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; /** * {@link AsyncEntityProducer} implementation that generates a data stream from the content at a {@link Path}. * * @since 5.2 */ public final class PathEntityProducer implements AsyncEntityProducer { private static final int BUFFER_SIZE = 8192; private final Path file; private final OpenOption[] openOptions; private final ByteBuffer byteBuffer; private final long length; private final ContentType contentType; private final boolean chunked; private final AtomicReference exception; private final AtomicReference channelRef; private boolean eof; public PathEntityProducer(final Path file, final ContentType contentType, final boolean chunked, final OpenOption... openOptions) throws IOException { this(file, BUFFER_SIZE, contentType, chunked, openOptions); } public PathEntityProducer(final Path file, final ContentType contentType, final OpenOption... openOptions) throws IOException { this(file, contentType, false, openOptions); } public PathEntityProducer(final Path file, final int bufferSize, final ContentType contentType, final boolean chunked, final OpenOption... openOptions) throws IOException { this.file = Args.notNull(file, "file"); this.openOptions = openOptions; this.length = Files.size(file); this.byteBuffer = ByteBuffer.allocate(bufferSize); this.contentType = contentType; this.chunked = chunked; this.channelRef = new AtomicReference<>(); this.exception = new AtomicReference<>(); } public PathEntityProducer(final Path file, final OpenOption... openOptions) throws IOException { this(file, ContentType.APPLICATION_OCTET_STREAM, openOptions); } @Override public int available() { return Integer.MAX_VALUE; } @Override public void failed(final Exception cause) { if (exception.compareAndSet(null, cause)) { releaseResources(); } } @Override public String getContentEncoding() { return null; } @Override public long getContentLength() { return length; } @Override public String getContentType() { return Objects.toString(contentType, null); } public Exception getException() { return exception.get(); } @Override public Set getTrailerNames() { return Collections.emptySet(); } @Override public boolean isChunked() { return chunked; } @Override public boolean isRepeatable() { return true; } @Override public void produce(final DataStreamChannel dataStreamChannel) throws IOException { SeekableByteChannel seekableByteChannel = channelRef.get(); if (seekableByteChannel == null) { seekableByteChannel = Files.newByteChannel(file, openOptions); Asserts.check(channelRef.getAndSet(seekableByteChannel) == null, "Illegal producer state"); } if (!eof) { final int bytesRead = seekableByteChannel.read(byteBuffer); if (bytesRead < 0) { eof = true; } } if (byteBuffer.position() > 0) { byteBuffer.flip(); dataStreamChannel.write(byteBuffer); byteBuffer.compact(); } if (eof && byteBuffer.position() == 0) { dataStreamChannel.endStream(); releaseResources(); } } @Override public void releaseResources() { eof = false; Closer.closeQuietly(channelRef.getAndSet(null)); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/StringAsyncEntityConsumer.java0100644 0000000 0000000 00000006471 00000250600 030663 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.nio.CharBuffer; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; /** * Basic {@link org.apache.hc.core5.http.nio.AsyncEntityConsumer} implementation * that processes the data stream content into a string. * * @since 5.0 */ public class StringAsyncEntityConsumer extends AbstractCharAsyncEntityConsumer { private final int capacityIncrement; private final CharArrayBuffer content; public StringAsyncEntityConsumer(final int bufSize, final int capacityIncrement, final CharCodingConfig charCodingConfig) { super(bufSize, charCodingConfig); this.capacityIncrement = Args.positive(capacityIncrement, "Capacity increment"); this.content = new CharArrayBuffer(1024); } public StringAsyncEntityConsumer(final int capacityIncrement) { this(DEF_BUF_SIZE, capacityIncrement, CharCodingConfig.DEFAULT); } public StringAsyncEntityConsumer(final CharCodingConfig charCodingConfig) { this(DEF_BUF_SIZE, Integer.MAX_VALUE, charCodingConfig); } public StringAsyncEntityConsumer() { this(Integer.MAX_VALUE); } @Override protected final void streamStart(final ContentType contentType) throws HttpException, IOException { } @Override protected int capacityIncrement() { final int available = content.capacity() - content.length(); return Math.max(capacityIncrement, available); } @Override protected final void data(final CharBuffer src, final boolean endOfStream) { Args.notNull(src, "CharBuffer"); final int chunk = src.remaining(); content.ensureCapacity(chunk); src.get(content.array(), content.length(), chunk); content.setLength(content.length() + chunk); } @Override public String generateContent() { return content.toString(); } @Override public void releaseResources() { content.clear(); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/StringAsyncEntityProducer.java0100644 0000000 0000000 00000006664 00000250600 030657 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.nio.CharBuffer; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.nio.StreamChannel; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; /** * Basic {@link org.apache.hc.core5.http.nio.AsyncDataProducer} implementation that * generates data stream from content of a string. * * @since 5.0 */ public class StringAsyncEntityProducer extends AbstractCharAsyncEntityProducer { private final CharBuffer content; private final AtomicReference exception; public StringAsyncEntityProducer( final CharSequence content, final int bufferSize, final int fragmentSizeHint, final ContentType contentType) { super(bufferSize, fragmentSizeHint, contentType); Args.notNull(content, "Content"); this.content = CharBuffer.wrap(content); this.exception = new AtomicReference<>(); } public StringAsyncEntityProducer(final CharSequence content, final int bufferSize, final ContentType contentType) { this(content, bufferSize, -1, contentType); } public StringAsyncEntityProducer(final CharSequence content, final ContentType contentType) { this(content, 4096, contentType); } public StringAsyncEntityProducer(final CharSequence content) { this(content, ContentType.TEXT_PLAIN); } @Override public boolean isRepeatable() { return true; } @Override protected int availableData() { return Integer.MAX_VALUE; } @Override protected void produceData(final StreamChannel channel) throws IOException { Asserts.notNull(channel, "Channel"); channel.write(content); if (!content.hasRemaining()) { channel.endStream(); } } @Override public void failed(final Exception cause) { if (exception.compareAndSet(null, cause)) { releaseResources(); } } public Exception getException() { return exception.get(); } @Override public void releaseResources() { this.content.clear(); super.releaseResources(); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/package-info.java0100644 0000000 0000000 00000002446 00000250600 026030 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * HTTP message entity APIs based on the asynchronous (non-blocking) I/O model. */ package org.apache.hc.core5.http.nio.entity; httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/0040755 0000000 0000000 00000000000 00000250600 022123 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/BasicClientTlsStrategy.java0100644 0000000 0000000 00000013234 00000250600 027354 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.ssl; import java.net.SocketAddress; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.reactor.ssl.SSLBufferMode; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; import org.apache.hc.core5.reactor.ssl.TransportSecurityLayer; import org.apache.hc.core5.ssl.SSLContexts; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; /** * Basic client-side implementation of {@link TlsStrategy} that upgrades outbound connections * with TLS security but does not make use of TLS ALPN extension to negotiate an application * protocol. This strategy will also automatically enforce restrictions on TLS parameters * by excluding TLS versions and ciphers considered weak. *

* This strategy should not be used unless no TLS ALPN support is actually desired. One is * advised to use a custom {@link TlsStrategy} implementation or an implementation provided * by the HTTP/2 module. * * @since 5.0 */ public class BasicClientTlsStrategy implements TlsStrategy { private final SSLContext sslContext; private final SSLBufferMode sslBufferMode; private final SSLSessionInitializer initializer; private final SSLSessionVerifier verifier; public BasicClientTlsStrategy( final SSLContext sslContext, final SSLBufferMode sslBufferMode, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier) { this.sslContext = Args.notNull(sslContext, "SSL context"); this.sslBufferMode = sslBufferMode; this.initializer = initializer; this.verifier = verifier; } public BasicClientTlsStrategy( final SSLContext sslContext, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier) { this(sslContext, null, initializer, verifier); } public BasicClientTlsStrategy( final SSLContext sslContext, final SSLSessionVerifier verifier) { this(sslContext, null, null, verifier); } public BasicClientTlsStrategy(final SSLContext sslContext) { this(sslContext, null, null, null); } public BasicClientTlsStrategy() { this(SSLContexts.createSystemDefault()); } /** * Constructor with the default SSL context based on system properties and custom {@link SSLSessionVerifier} verifier. * @param verifier the custom {@link SSLSessionVerifier}. * @see SSLContext * @since 5.2 */ public BasicClientTlsStrategy(final SSLSessionVerifier verifier) { this(SSLContexts.createSystemDefault(), verifier); } @Override public void upgrade( final TransportSecurityLayer tlsSession, final NamedEndpoint endpoint, final Object attachment, final Timeout handshakeTimeout, final FutureCallback callback) { tlsSession.startTls( sslContext, endpoint, sslBufferMode, (e, sslEngine) -> { final SSLParameters sslParameters = sslEngine.getSSLParameters(); sslParameters.setEndpointIdentificationAlgorithm(URIScheme.HTTPS.id); sslEngine.setSSLParameters(TlsSupport.enforceStrongSecurity(sslParameters)); if (initializer != null) { initializer.initialize(e, sslEngine); } }, verifier, handshakeTimeout, callback); } /** * @deprecated use {@link #upgrade(TransportSecurityLayer, NamedEndpoint, Object, Timeout, FutureCallback)} */ @Deprecated @Override public boolean upgrade( final TransportSecurityLayer tlsSession, final HttpHost host, final SocketAddress localAddress, final SocketAddress remoteAddress, final Object attachment, final Timeout handshakeTimeout) { final String scheme = host != null ? host.getSchemeName() : null; if (URIScheme.HTTPS.same(scheme)) { upgrade(tlsSession, host, attachment, handshakeTimeout, null); return true; } return false; } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/BasicServerTlsStrategy.java0100644 0000000 0000000 00000016667 00000250600 027421 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.ssl; import java.net.SocketAddress; import javax.net.ssl.SSLContext; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.reactor.ssl.SSLBufferMode; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; import org.apache.hc.core5.reactor.ssl.TransportSecurityLayer; import org.apache.hc.core5.ssl.SSLContexts; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; /** * Basic server-side implementation of {@link TlsStrategy} that upgrades inbound connection with * TLS security but does not make use of TLS ALPN extension to negotiate an application * protocol. This strategy will also automatically enforce restrictions on TLS parameters * by excluding TLS versions and ciphers considered weak. *

* This strategy should not be used unless no TLS ALPN support is actually desired. * One is advised to use a custom {@link TlsStrategy} implementation or an implementation * provided by the HTTP/2 module. * * @since 5.0 */ public class BasicServerTlsStrategy implements TlsStrategy { private final SSLContext sslContext; @SuppressWarnings("deprecation") private final SecurePortStrategy securePortStrategy; private final SSLBufferMode sslBufferMode; private final SSLSessionInitializer initializer; private final SSLSessionVerifier verifier; /** * @deprecated Use {@link BasicServerTlsStrategy#BasicServerTlsStrategy(SSLContext, SSLBufferMode, SSLSessionInitializer, SSLSessionVerifier)} */ @Deprecated public BasicServerTlsStrategy( final SSLContext sslContext, final SecurePortStrategy securePortStrategy, final SSLBufferMode sslBufferMode, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier) { this.sslContext = Args.notNull(sslContext, "SSL context"); this.securePortStrategy = securePortStrategy; this.sslBufferMode = sslBufferMode; this.initializer = initializer; this.verifier = verifier; } /** * @deprecated Use {@link BasicServerTlsStrategy#BasicServerTlsStrategy(SSLContext, SSLSessionInitializer, SSLSessionVerifier)} */ @Deprecated public BasicServerTlsStrategy( final SSLContext sslContext, final SecurePortStrategy securePortStrategy, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier) { this(sslContext, securePortStrategy, null, initializer, verifier); } /** * @deprecated Use {@link BasicServerTlsStrategy#BasicServerTlsStrategy(SSLContext, SSLSessionVerifier)} */ @Deprecated public BasicServerTlsStrategy( final SSLContext sslContext, final SecurePortStrategy securePortStrategy, final SSLSessionVerifier verifier) { this(sslContext, securePortStrategy, null, null, verifier); } /** * @deprecated Use {@link BasicServerTlsStrategy#BasicServerTlsStrategy(SSLContext)} */ @Deprecated public BasicServerTlsStrategy(final SSLContext sslContext, final SecurePortStrategy securePortStrategy) { this(sslContext, securePortStrategy, null, null, null); } /** * @deprecated Use {@link BasicServerTlsStrategy#BasicServerTlsStrategy()} */ @Deprecated public BasicServerTlsStrategy(final SecurePortStrategy securePortStrategy) { this(SSLContexts.createSystemDefault(), securePortStrategy); } /** * Constructor with the default SSL context based on system properties and custom {@link SSLSessionVerifier} verifier. * @param verifier the custom {@link SSLSessionVerifier}. * @since 5.2 */ public BasicServerTlsStrategy(final SSLSessionVerifier verifier) { this(SSLContexts.createSystemDefault(), verifier); } public BasicServerTlsStrategy( final SSLContext sslContext, final SSLBufferMode sslBufferMode, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier) { this.sslContext = Args.notNull(sslContext, "SSL context"); this.sslBufferMode = sslBufferMode; this.initializer = initializer; this.verifier = verifier; this.securePortStrategy = null; } public BasicServerTlsStrategy( final SSLContext sslContext, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier) { this(sslContext, (SSLBufferMode) null, initializer, verifier); } public BasicServerTlsStrategy( final SSLContext sslContext, final SSLSessionVerifier verifier) { this(sslContext, (SSLBufferMode) null, null, verifier); } public BasicServerTlsStrategy(final SSLContext sslContext) { this(sslContext, null, null, null, null); } public BasicServerTlsStrategy() { this(SSLContexts.createSystemDefault()); } private boolean isApplicable(final SocketAddress localAddress) { return securePortStrategy == null || securePortStrategy.isSecure(localAddress); } @Override public void upgrade( final TransportSecurityLayer tlsSession, final NamedEndpoint endpoint, final Object attachment, final Timeout handshakeTimeout, final FutureCallback callback) { tlsSession.startTls(sslContext, endpoint, sslBufferMode, TlsSupport.enforceStrongSecurity(initializer), verifier, handshakeTimeout, callback); } /** * @deprecated use {@link #upgrade(TransportSecurityLayer, NamedEndpoint, Object, Timeout, FutureCallback)} */ @Deprecated @Override public boolean upgrade( final TransportSecurityLayer tlsSession, final HttpHost host, final SocketAddress localAddress, final SocketAddress remoteAddress, final Object attachment, final Timeout handshakeTimeout) { if (isApplicable(localAddress)) { upgrade(tlsSession, host, attachment, handshakeTimeout, null); return true; } return false; } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/FixedPortStrategy.java0100644 0000000 0000000 00000004070 00000250600 026413 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.ssl; import java.net.InetSocketAddress; import java.net.SocketAddress; import org.apache.hc.core5.util.Args; /** * Basic implementation of {@link SecurePortStrategy} with a fixed list of secure ports. * * @since 5.0 * * @deprecated Use configuration parameters provided by connection listeners. */ @Deprecated public final class FixedPortStrategy implements SecurePortStrategy { private final int[] securePorts; public FixedPortStrategy(final int... securePorts) { this.securePorts = Args.notNull(securePorts, "Secure ports"); } @Override public boolean isSecure(final SocketAddress localAddress) { final int port = ((InetSocketAddress) localAddress).getPort(); for (final int securePort: securePorts) { if (port == securePort) { return true; } } return false; } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/SecurePortStrategy.java0100644 0000000 0000000 00000003277 00000250600 026612 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.ssl; import java.net.SocketAddress; /** * Side-side strategy to determine if local endpoint should be secured with TLS. * * @since 5.0 * * @deprecated Use configuration parameters provided by connection listeners. */ @Deprecated public interface SecurePortStrategy { /** * Determines if the given address should be secured or considered secure. * @param localAddress the given address. * * @return secure flag. */ boolean isSecure(SocketAddress localAddress); } httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/TlsStrategy.java0100644 0000000 0000000 00000007152 00000250600 025255 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.ssl; import java.net.SocketAddress; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.reactor.ssl.TransportSecurityLayer; import org.apache.hc.core5.util.Timeout; /** * TLS protocol upgrade strategy for non-blocking {@link TransportSecurityLayer} sessions. * * @since 5.0 */ public interface TlsStrategy { /** * Secures current session layer with TLS. * * @param sessionLayer the session layer * @param host the name of the opposite endpoint when given or {@code null} otherwise. * @param localAddress the address of the local endpoint. * @param remoteAddress the address of the remote endpoint. * @param attachment arbitrary object passes to the TLS session initialization code. * @param handshakeTimeout the timeout to use while performing the TLS handshake; may be {@code null}. * @return {@code true} if the session has been upgraded, {@code false} otherwise. * * @deprecated use {@link #upgrade(TransportSecurityLayer, NamedEndpoint, Object, Timeout, FutureCallback)} */ @Deprecated boolean upgrade( TransportSecurityLayer sessionLayer, HttpHost host, SocketAddress localAddress, SocketAddress remoteAddress, Object attachment, Timeout handshakeTimeout); /** * Secures current session layer with TLS. * * @param sessionLayer the session layer * @param endpoint the name of the opposite endpoint when applicable or {@code null} otherwise. * @param attachment arbitrary object passes to the TLS session initialization code. * @param handshakeTimeout the timeout to use while performing the TLS handshake; may be {@code null}. * @param callback Operation result callback. * * @since 5.2 */ default void upgrade( TransportSecurityLayer sessionLayer, NamedEndpoint endpoint, Object attachment, Timeout handshakeTimeout, FutureCallback callback) { upgrade(sessionLayer, new HttpHost(URIScheme.HTTPS.id, endpoint.getHostName(), endpoint.getPort()), null, null, attachment, handshakeTimeout); if (callback != null) { callback.completed(sessionLayer); } } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/TlsSupport.java0100644 0000000 0000000 00000004256 00000250600 025131 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.ssl; import javax.net.ssl.SSLParameters; import org.apache.hc.core5.http.ssl.TLS; import org.apache.hc.core5.http.ssl.TlsCiphers; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; /** * HTTP/1.1 TLS support methods * * @since 5.0 */ public final class TlsSupport { /** * @since 5.3 */ public static SSLParameters enforceStrongSecurity(final SSLParameters sslParameters) { sslParameters.setProtocols(TLS.excludeWeak(sslParameters.getProtocols())); sslParameters.setCipherSuites(TlsCiphers.excludeWeak(sslParameters.getCipherSuites())); return sslParameters; } public static SSLSessionInitializer enforceStrongSecurity(final SSLSessionInitializer initializer) { return (endpoint, sslEngine) -> { sslEngine.setSSLParameters(enforceStrongSecurity(sslEngine.getSSLParameters())); if (initializer != null) { initializer.initialize(endpoint, sslEngine); } }; } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/TlsUpgradeCapable.java0100644 0000000 0000000 00000003143 00000250600 026306 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.ssl; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.reactor.ProtocolIOSession; /** * Capability to upgrade to TLS. * * @since 5.2 */ @Internal public interface TlsUpgradeCapable { void tlsUpgrade(NamedEndpoint endpoint, final FutureCallback callback); } httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/package-info.java0100644 0000000 0000000 00000002354 00000250600 025313 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * TLS protocol support. */ package org.apache.hc.core5.http.nio.ssl; httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/0040755 0000000 0000000 00000000000 00000250600 023036 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractAsyncPushHandler.java0100644 0000000 0000000 00000011561 00000250600 030601 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.AsyncResponseConsumer; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * Abstract push response handler. * * @since 5.0 * * @param response message representation. */ public abstract class AbstractAsyncPushHandler implements AsyncPushConsumer { private final AsyncResponseConsumer responseConsumer; private final AtomicReference promiseRef; public AbstractAsyncPushHandler(final AsyncResponseConsumer responseConsumer) { this.responseConsumer = Args.notNull(responseConsumer, "Response consumer"); this.promiseRef = new AtomicReference<>(); } /** * Triggered to handle the push message with the given promised request. * * @param promise the promised request message. * @param responseMessage the pushed response message. * @throws IOException in case of an I/O error. * @throws HttpException in case of an HTTP protocol violation. */ protected abstract void handleResponse( final HttpRequest promise, final T responseMessage) throws IOException, HttpException; /** * Triggered to handle the exception thrown while processing a push response. * * @param promise the promised request message. * @param cause the cause of error. */ protected void handleError(final HttpRequest promise, final Exception cause) { } @Override public final void consumePromise( final HttpRequest promise, final HttpResponse response, final EntityDetails entityDetails, final HttpContext httpContext) throws HttpException, IOException { promiseRef.compareAndSet(null, promise); responseConsumer.consumeResponse(response, entityDetails, httpContext, new FutureCallback() { @Override public void completed(final T result) { try { handleResponse(promise, result); } catch (final Exception ex) { failed(ex); } } @Override public void failed(final Exception cause) { releaseResources(); } @Override public void cancelled() { releaseResources(); } }); } @Override public final void updateCapacity(final CapacityChannel capacityChannel) throws IOException { responseConsumer.updateCapacity(capacityChannel); } @Override public final void consume(final ByteBuffer src) throws IOException { responseConsumer.consume(src); } @Override public final void streamEnd(final List trailers) throws HttpException, IOException { responseConsumer.streamEnd(trailers); } @Override public final void failed(final Exception cause) { responseConsumer.failed(cause); handleError(promiseRef.get(), cause); releaseResources(); } @Override public final void releaseResources() { if (responseConsumer != null) { responseConsumer.releaseResources(); } } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractAsyncRequesterConsumer.java0100644 0000000 0000000 00000013456 00000250600 032064 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.UnsupportedCharsetException; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.concurrent.CallbackContribution; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.nio.AsyncEntityConsumer; import org.apache.hc.core5.http.nio.AsyncRequestConsumer; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * Abstract asynchronous request consumer that makes use of {@link AsyncEntityConsumer} * to process request message content. * * @param request processing result representation. * @param request entity representation. * * @since 5.0 */ public abstract class AbstractAsyncRequesterConsumer implements AsyncRequestConsumer { private final Supplier> dataConsumerSupplier; private final AtomicReference> dataConsumerRef; public AbstractAsyncRequesterConsumer(final Supplier> dataConsumerSupplier) { this.dataConsumerSupplier = Args.notNull(dataConsumerSupplier, "Data consumer supplier"); this.dataConsumerRef = new AtomicReference<>(); } public AbstractAsyncRequesterConsumer(final AsyncEntityConsumer dataConsumer) { this(() -> dataConsumer); } /** * Triggered to generate object that represents a result of request message processing. * @param request the request message. * @param entity the request entity. * @param contentType the request content type. * @return the result of request processing. */ protected abstract T buildResult(HttpRequest request, E entity, ContentType contentType); @Override public final void consumeRequest( final HttpRequest request, final EntityDetails entityDetails, final HttpContext httpContext, final FutureCallback resultCallback) throws HttpException, IOException { if (entityDetails != null) { final AsyncEntityConsumer dataConsumer = dataConsumerSupplier.get(); if (dataConsumer == null) { throw new HttpException("Supplied data consumer is null"); } dataConsumerRef.set(dataConsumer); dataConsumer.streamStart(entityDetails, new CallbackContribution(resultCallback) { @Override public void completed(final E entity) { final ContentType contentType; try { contentType = ContentType.parse(entityDetails.getContentType()); final T result = buildResult(request, entity, contentType); resultCallback.completed(result); } catch (final UnsupportedCharsetException ex) { resultCallback.failed(ex); } } }); } else { resultCallback.completed(buildResult(request, null, null)); } } @Override public final void updateCapacity(final CapacityChannel capacityChannel) throws IOException { final AsyncEntityConsumer dataConsumer = dataConsumerRef.get(); if (dataConsumer != null) { dataConsumer.updateCapacity(capacityChannel); } else { capacityChannel.update(Integer.MAX_VALUE); } } @Override public final void consume(final ByteBuffer src) throws IOException { final AsyncEntityConsumer dataConsumer = dataConsumerRef.get(); if (dataConsumer != null) { dataConsumer.consume(src); } } @Override public final void streamEnd(final List trailers) throws HttpException, IOException { final AsyncEntityConsumer dataConsumer = dataConsumerRef.get(); if (dataConsumer != null) { dataConsumer.streamEnd(trailers); } } @Override public final void failed(final Exception cause) { releaseResources(); } @Override public final void releaseResources() { final AsyncEntityConsumer dataConsumer = dataConsumerRef.getAndSet(null); if (dataConsumer != null) { dataConsumer.releaseResources(); } } }httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractAsyncResponseConsumer.java0100644 0000000 0000000 00000014110 00000250600 031667 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.UnsupportedCharsetException; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.concurrent.CallbackContribution; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.nio.AsyncEntityConsumer; import org.apache.hc.core5.http.nio.AsyncResponseConsumer; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * Abstract asynchronous response consumer that makes use of {@link AsyncEntityConsumer} * to process response message content. * * @param response processing result representation. * @param response entity representation. * * @since 5.0 */ public abstract class AbstractAsyncResponseConsumer implements AsyncResponseConsumer { private final Supplier> dataConsumerSupplier; private final AtomicReference> dataConsumerRef; public AbstractAsyncResponseConsumer(final Supplier> dataConsumerSupplier) { this.dataConsumerSupplier = Args.notNull(dataConsumerSupplier, "Data consumer supplier"); this.dataConsumerRef = new AtomicReference<>(); } public AbstractAsyncResponseConsumer(final AsyncEntityConsumer dataConsumer) { this(() -> dataConsumer); } /** * Triggered to generate object that represents a result of response message processing. * @param response the response message. * @param entity the response entity. * @param contentType the response content type. * @return the result of response processing. */ protected abstract T buildResult(HttpResponse response, E entity, ContentType contentType); @Override public final void consumeResponse( final HttpResponse response, final EntityDetails entityDetails, final HttpContext httpContext, final FutureCallback resultCallback) throws HttpException, IOException { if (entityDetails != null) { final AsyncEntityConsumer dataConsumer = dataConsumerSupplier.get(); if (dataConsumer == null) { throw new HttpException("Supplied data consumer is null"); } dataConsumerRef.set(dataConsumer); dataConsumer.streamStart(entityDetails, new CallbackContribution(resultCallback) { @Override public void completed(final E entity) { final ContentType contentType; try { contentType = ContentType.parse(entityDetails.getContentType()); final T result = buildResult(response, entity, contentType); if (resultCallback != null) { resultCallback.completed(result); } } catch (final UnsupportedCharsetException ex) { if (resultCallback != null) { resultCallback.failed(ex); } } } }); } else { final T result = buildResult(response, null, null); if (resultCallback != null) { resultCallback.completed(result); } } } @Override public final void updateCapacity(final CapacityChannel capacityChannel) throws IOException { final AsyncEntityConsumer dataConsumer = dataConsumerRef.get(); if (dataConsumer != null) { dataConsumer.updateCapacity(capacityChannel); } else { capacityChannel.update(Integer.MAX_VALUE); } } @Override public final void consume(final ByteBuffer src) throws IOException { final AsyncEntityConsumer dataConsumer = dataConsumerRef.get(); if (dataConsumer != null) { dataConsumer.consume(src); } } @Override public final void streamEnd(final List trailers) throws HttpException, IOException { final AsyncEntityConsumer dataConsumer = dataConsumerRef.get(); if (dataConsumer != null) { dataConsumer.streamEnd(trailers); } } @Override public final void failed(final Exception cause) { releaseResources(); } @Override public final void releaseResources() { final AsyncEntityConsumer dataConsumer = dataConsumerRef.getAndSet(null); if (dataConsumer != null) { dataConsumer.releaseResources(); } } }httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractAsyncServerAuthFilter.java0100644 0000000 0000000 00000016674 00000250600 031634 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.nio.AsyncDataConsumer; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.AsyncFilterChain; import org.apache.hc.core5.http.nio.AsyncFilterHandler; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.net.URIAuthority; /** * Abstract asynchronous HTTP request filter that implements standard HTTP authentication handshake. * * @param authorization token representation. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.STATELESS) public abstract class AbstractAsyncServerAuthFilter implements AsyncFilterHandler { private final boolean respondImmediately; protected AbstractAsyncServerAuthFilter(final boolean respondImmediately) { this.respondImmediately = respondImmediately; } /** * Parses authorization header value into an authentication token sent by the client * as a response to an authentication challenge. * * @param authorizationValue the authorization header value. * @param context the actual execution context. * @return authorization token * @throws HttpException in case of an HTTP protocol violation. */ protected abstract T parseChallengeResponse(String authorizationValue, HttpContext context) throws HttpException; /** * Authenticates the client using the authentication token sent by the client * as a response to an authentication challenge. * * @param challengeResponse the authentication token sent by the client * as a response to an authentication challenge. * @param authority the URI authority. * @param requestUri the request URI. * @param context the actual execution context. * @return {@code true} if the client could be successfully authenticated {@code false} otherwise. */ protected abstract boolean authenticate(T challengeResponse, URIAuthority authority, String requestUri, HttpContext context); /** * Generates an authentication challenge in case of unsuccessful authentication. * * @param challengeResponse the authentication token sent by the client * as a response to an authentication challenge * or {@code null} if the client has not sent any. * @param authority the URI authority. * @param requestUri the request URI. * @param context the actual execution context. * @return an authorization challenge value. */ protected abstract String generateChallenge(T challengeResponse, URIAuthority authority, String requestUri, HttpContext context); /** * Generates response body for UNAUTHORIZED response. * * @param unauthorized the response to return as a result of authentication failure. * @return the response content entity. */ protected AsyncEntityProducer generateResponseContent(final HttpResponse unauthorized) { return AsyncEntityProducers.create("Unauthorized"); } @Override public final AsyncDataConsumer handle( final HttpRequest request, final EntityDetails entityDetails, final HttpContext context, final AsyncFilterChain.ResponseTrigger responseTrigger, final AsyncFilterChain chain) throws HttpException, IOException { final Header h = request.getFirstHeader(HttpHeaders.AUTHORIZATION); final T challengeResponse = h != null ? parseChallengeResponse(h.getValue(), context) : null; final URIAuthority authority = request.getAuthority(); final String requestUri = request.getRequestUri(); final boolean authenticated = authenticate(challengeResponse, authority, requestUri, context); final Header expect = request.getFirstHeader(HttpHeaders.EXPECT); final boolean expectContinue = expect != null && HeaderElements.CONTINUE.equalsIgnoreCase(expect.getValue()); if (authenticated) { if (expectContinue) { responseTrigger.sendInformation(new BasicClassicHttpResponse(HttpStatus.SC_CONTINUE)); } return chain.proceed(request, entityDetails, context, responseTrigger); } final HttpResponse unauthorized = new BasicHttpResponse(HttpStatus.SC_UNAUTHORIZED); unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, generateChallenge(challengeResponse, authority, requestUri, context)); final AsyncEntityProducer responseContentProducer = generateResponseContent(unauthorized); if (respondImmediately || expectContinue || entityDetails == null) { responseTrigger.submitResponse(unauthorized, responseContentProducer); return null; } return new AsyncDataConsumer() { @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { capacityChannel.update(Integer.MAX_VALUE); } @Override public void consume(final ByteBuffer src) throws IOException { } @Override public void streamEnd(final List trailers) throws HttpException, IOException { responseTrigger.submitResponse(unauthorized, responseContentProducer); } @Override public void releaseResources() { if (responseContentProducer != null) { responseContentProducer.releaseResources(); } } }; } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractServerExchangeHandler.java0100644 0000000 0000000 00000023656 00000250600 031605 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.nio.AsyncPushProducer; import org.apache.hc.core5.http.nio.AsyncRequestConsumer; import org.apache.hc.core5.http.nio.AsyncResponseProducer; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.AsyncServerRequestHandler; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Asserts; /** * Abstract server side message exchange handler. * * @param the type of request messages. * @since 5.0 */ public abstract class AbstractServerExchangeHandler implements AsyncServerExchangeHandler { private final AtomicReference> requestConsumerRef; private final AtomicReference responseProducerRef; public AbstractServerExchangeHandler() { this.requestConsumerRef = new AtomicReference<>(); this.responseProducerRef = new AtomicReference<>(); } /** * Triggered to supply a request consumer to process the incoming request. * @param request the request message. * @param entityDetails the request entity details. * @param context the actual execution context. * @return the request consumer. * @throws HttpException in case of an HTTP protocol violation. */ protected abstract AsyncRequestConsumer supplyConsumer( HttpRequest request, EntityDetails entityDetails, HttpContext context) throws HttpException; /** * Triggered to handles the request object produced by the {@link AsyncRequestConsumer} returned * from the {@link #supplyConsumer(HttpRequest, EntityDetails, HttpContext)} method. The handler * can choose to send response messages immediately inside the call or asynchronously * at some later point. * * @param requestMessage the request message. * @param responseTrigger the response trigger. * @param context the actual execution context. * @throws HttpException in case of an HTTP protocol violation. * @throws IOException in case of an I/O error. */ protected abstract void handle( T requestMessage, AsyncServerRequestHandler.ResponseTrigger responseTrigger, HttpContext context) throws HttpException, IOException; @Override public final void handleRequest( final HttpRequest request, final EntityDetails entityDetails, final ResponseChannel responseChannel, final HttpContext context) throws HttpException, IOException { final AsyncRequestConsumer requestConsumer = supplyConsumer(request, entityDetails, context); if (requestConsumer == null) { throw new HttpException("Unable to handle request"); } requestConsumerRef.set(requestConsumer); final AsyncServerRequestHandler.ResponseTrigger responseTrigger = new AsyncServerRequestHandler.ResponseTrigger() { @Override public void sendInformation(final HttpResponse response, final HttpContext httpContext) throws HttpException, IOException { responseChannel.sendInformation(response, httpContext); } @Override public void submitResponse( final AsyncResponseProducer producer, final HttpContext httpContext) throws HttpException, IOException { if (responseProducerRef.compareAndSet(null, producer)) { producer.sendResponse(responseChannel, httpContext); } } @Override public void pushPromise( final HttpRequest promise, final HttpContext httpContext, final AsyncPushProducer pushProducer) throws HttpException, IOException { responseChannel.pushPromise(promise, pushProducer, httpContext); } @Override public String toString() { return "Response trigger: " + responseChannel; } }; requestConsumer.consumeRequest(request, entityDetails, context, new FutureCallback() { @Override public void completed(final T result) { try { handle(result, responseTrigger, context); } catch (final HttpException ex) { try { responseTrigger.submitResponse( AsyncResponseBuilder.create(HttpStatus.SC_INTERNAL_SERVER_ERROR) .setEntity(ex.getMessage()) .build(), context); } catch (final HttpException | IOException ex2) { failedInternal(ex2); } } catch (final IOException ex) { failedInternal(ex); } finally { releaseRequestConsumer(); } } @Override public void failed(final Exception ex) { failedInternal(ex); } @Override public void cancelled() { releaseResourcesInternal(); } }); } @Override public final void updateCapacity(final CapacityChannel capacityChannel) throws IOException { final AsyncRequestConsumer requestConsumer = requestConsumerRef.get(); if (requestConsumer != null) { requestConsumer.updateCapacity(capacityChannel); } else { capacityChannel.update(Integer.MAX_VALUE); } } @Override public final void consume(final ByteBuffer src) throws IOException { final AsyncRequestConsumer requestConsumer = requestConsumerRef.get(); if (requestConsumer != null) { requestConsumer.consume(src); } } @Override public final void streamEnd(final List trailers) throws HttpException, IOException { final AsyncRequestConsumer requestConsumer = requestConsumerRef.get(); if (requestConsumer != null) { requestConsumer.streamEnd(trailers); } } @Override public final int available() { final AsyncResponseProducer dataProducer = responseProducerRef.get(); return dataProducer != null ? dataProducer.available() : 0; } @Override public final void produce(final DataStreamChannel channel) throws IOException { final AsyncResponseProducer dataProducer = responseProducerRef.get(); Asserts.notNull(dataProducer, "Data producer"); dataProducer.produce(channel); } @Override public final void failed(final Exception cause) { failedInternal(cause); } void failedInternal(final Exception cause) { try { final AsyncRequestConsumer requestConsumer = requestConsumerRef.get(); if (requestConsumer != null) { requestConsumer.failed(cause); } } finally { releaseRequestConsumer(); } try { final AsyncResponseProducer dataProducer = responseProducerRef.get(); if (dataProducer != null) { dataProducer.failed(cause); } } finally { releaseResponseProducer(); } } private void releaseRequestConsumer() { final AsyncRequestConsumer requestConsumer = requestConsumerRef.getAndSet(null); if (requestConsumer != null) { requestConsumer.releaseResources(); } } private void releaseResponseProducer() { final AsyncResponseProducer dataProducer = responseProducerRef.getAndSet(null); if (dataProducer != null) { dataProducer.releaseResources(); } } private void releaseResourcesInternal() { releaseResponseProducer(); releaseRequestConsumer(); } @Override public final void releaseResources() { // Note even though the message exchange has been fully // completed on the transport level, the request // consumer may still be busy post-processing // the request message. releaseResponseProducer(); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncPushBuilder.java0100644 0000000 0000000 00000011561 00000250600 027126 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.AsyncPushProducer; import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityProducer; import org.apache.hc.core5.http.support.AbstractResponseBuilder; import org.apache.hc.core5.util.Args; import java.util.Arrays; /** * Builder for {@link AsyncPushProducer} instances. * * @since 5.0 */ public class AsyncPushBuilder extends AbstractResponseBuilder { private AsyncEntityProducer entityProducer; AsyncPushBuilder(final int status) { super(status); } public static AsyncPushBuilder create(final int status) { Args.checkRange(status, 100, 599, "HTTP status code"); return new AsyncPushBuilder(status); } @Override public AsyncPushBuilder setVersion(final ProtocolVersion version) { super.setVersion(version); return this; } @Override public AsyncPushBuilder setHeaders(final Header... headers) { super.setHeaders(headers); return this; } @Override public AsyncPushBuilder addHeader(final Header header) { super.addHeader(header); return this; } @Override public AsyncPushBuilder addHeader(final String name, final String value) { super.addHeader(name, value); return this; } @Override public AsyncPushBuilder removeHeader(final Header header) { super.removeHeader(header); return this; } @Override public AsyncPushBuilder removeHeaders(final String name) { super.removeHeaders(name); return this; } @Override public AsyncPushBuilder setHeader(final Header header) { super.setHeader(header); return this; } @Override public AsyncPushBuilder setHeader(final String name, final String value) { super.setHeader(name, value); return this; } public AsyncEntityProducer getEntity() { return entityProducer; } public AsyncPushBuilder setEntity(final AsyncEntityProducer entityProducer) { this.entityProducer = entityProducer; return this; } public AsyncPushBuilder setEntity(final String content, final ContentType contentType) { this.entityProducer = new BasicAsyncEntityProducer(content, contentType); return this; } public AsyncPushBuilder setEntity(final String content) { this.entityProducer = new BasicAsyncEntityProducer(content); return this; } public AsyncPushBuilder setEntity(final byte[] content, final ContentType contentType) { this.entityProducer = new BasicAsyncEntityProducer(content, contentType); return this; } @Override public AsyncPushProducer build() { final HttpResponse response = new BasicHttpResponse(getStatus()); response.setVersion(getVersion()); response.setHeaders(getHeaders()); return new BasicPushProducer(response, entityProducer); } @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("AsyncPushProducer [status="); builder.append(getStatus()); builder.append(", headerGroup="); builder.append(Arrays.toString(getHeaders())); builder.append(", entity="); builder.append(entityProducer != null ? entityProducer.getClass() : null); builder.append("]"); return builder.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncRequestBuilder.java0100644 0000000 0000000 00000033504 00000250600 027640 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.AsyncRequestProducer; import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityProducer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer; import org.apache.hc.core5.http.support.AbstractRequestBuilder; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.net.URIBuilder; import org.apache.hc.core5.net.WWWFormCodec; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TextUtils; /** * Builder for {@link AsyncRequestProducer} instances. *

* Please note that this class treats parameters differently depending on composition * of the request: if the request has a content entity explicitly set with * {@link #setEntity(AsyncEntityProducer)} or it is not an entity enclosing method * (such as POST or PUT), parameters will be added to the query component of the request URI. * Otherwise, parameters will be added as a URL encoded entity. *

* * @since 5.0 */ public class AsyncRequestBuilder extends AbstractRequestBuilder { private AsyncEntityProducer entityProducer; AsyncRequestBuilder(final String method) { super(method); } AsyncRequestBuilder(final Method method) { super(method); } AsyncRequestBuilder(final String method, final URI uri) { super(method, uri); } AsyncRequestBuilder(final Method method, final URI uri) { this(method.name(), uri); } AsyncRequestBuilder(final Method method, final String uri) { this(method.name(), uri != null ? URI.create(uri) : null); } AsyncRequestBuilder(final String method, final String uri) { this(method, uri != null ? URI.create(uri) : null); } public static AsyncRequestBuilder create(final String method) { Args.notBlank(method, "HTTP method"); return new AsyncRequestBuilder(method); } public static AsyncRequestBuilder get() { return new AsyncRequestBuilder(Method.GET); } public static AsyncRequestBuilder get(final URI uri) { return new AsyncRequestBuilder(Method.GET, uri); } public static AsyncRequestBuilder get(final String uri) { return new AsyncRequestBuilder(Method.GET, uri); } public static AsyncRequestBuilder head() { return new AsyncRequestBuilder(Method.HEAD); } public static AsyncRequestBuilder head(final URI uri) { return new AsyncRequestBuilder(Method.HEAD, uri); } public static AsyncRequestBuilder head(final String uri) { return new AsyncRequestBuilder(Method.HEAD, uri); } /** * Initializes a new {@link AsyncRequestBuilder} instance for the {@code QUERY} method. * * @see Method#QUERY for more information regarding the properties of the {@code QUERY} method. * * @since 5.4 */ @Experimental public static AsyncRequestBuilder query() { return new AsyncRequestBuilder(Method.QUERY); } /** * Initializes a new {@link AsyncRequestBuilder} instance for the {@code QUERY} method. * * @param uri the request URI. * @see Method#QUERY for more information regarding the properties of the {@code QUERY} method. * * @since 5.4 */ @Experimental public static AsyncRequestBuilder query(final URI uri) { return new AsyncRequestBuilder(Method.QUERY, uri); } /** * Initializes a new {@link AsyncRequestBuilder} instance for the {@code QUERY} method. * * @param uri the request URI. * @see Method#QUERY for more information regarding the properties of the {@code QUERY} method. * * @since 5.4 */ @Experimental public static AsyncRequestBuilder query(final String uri) { return new AsyncRequestBuilder(Method.QUERY, uri); } public static AsyncRequestBuilder patch() { return new AsyncRequestBuilder(Method.PATCH); } public static AsyncRequestBuilder patch(final URI uri) { return new AsyncRequestBuilder(Method.PATCH, uri); } public static AsyncRequestBuilder patch(final String uri) { return new AsyncRequestBuilder(Method.PATCH, uri); } public static AsyncRequestBuilder post() { return new AsyncRequestBuilder(Method.POST); } public static AsyncRequestBuilder post(final URI uri) { return new AsyncRequestBuilder(Method.POST, uri); } public static AsyncRequestBuilder post(final String uri) { return new AsyncRequestBuilder(Method.POST, uri); } public static AsyncRequestBuilder put() { return new AsyncRequestBuilder(Method.PUT); } public static AsyncRequestBuilder put(final URI uri) { return new AsyncRequestBuilder(Method.PUT, uri); } public static AsyncRequestBuilder put(final String uri) { return new AsyncRequestBuilder(Method.PUT, uri); } public static AsyncRequestBuilder delete() { return new AsyncRequestBuilder(Method.DELETE); } public static AsyncRequestBuilder delete(final URI uri) { return new AsyncRequestBuilder(Method.DELETE, uri); } public static AsyncRequestBuilder delete(final String uri) { return new AsyncRequestBuilder(Method.DELETE, uri); } public static AsyncRequestBuilder trace() { return new AsyncRequestBuilder(Method.TRACE); } public static AsyncRequestBuilder trace(final URI uri) { return new AsyncRequestBuilder(Method.TRACE, uri); } public static AsyncRequestBuilder trace(final String uri) { return new AsyncRequestBuilder(Method.TRACE, uri); } public static AsyncRequestBuilder options() { return new AsyncRequestBuilder(Method.OPTIONS); } public static AsyncRequestBuilder options(final URI uri) { return new AsyncRequestBuilder(Method.OPTIONS, uri); } public static AsyncRequestBuilder options(final String uri) { return new AsyncRequestBuilder(Method.OPTIONS, uri); } @Override public AsyncRequestBuilder setVersion(final ProtocolVersion version) { super.setVersion(version); return this; } @Override public AsyncRequestBuilder setUri(final URI uri) { super.setUri(uri); return this; } @Override public AsyncRequestBuilder setUri(final String uri) { super.setUri(uri); return this; } @Override public AsyncRequestBuilder setScheme(final String scheme) { super.setScheme(scheme); return this; } @Override public AsyncRequestBuilder setAuthority(final URIAuthority authority) { super.setAuthority(authority); return this; } /** * @since 5.1 */ @Override public AsyncRequestBuilder setHttpHost(final HttpHost httpHost) { super.setHttpHost(httpHost); return this; } @Override public AsyncRequestBuilder setPath(final String path) { super.setPath(path); return this; } @Override public AsyncRequestBuilder setHeaders(final Header... headers) { super.setHeaders(headers); return this; } @Override public AsyncRequestBuilder addHeader(final Header header) { super.addHeader(header); return this; } @Override public AsyncRequestBuilder addHeader(final String name, final String value) { super.addHeader(name, value); return this; } @Override public AsyncRequestBuilder removeHeader(final Header header) { super.removeHeader(header); return this; } @Override public AsyncRequestBuilder removeHeaders(final String name) { super.removeHeaders(name); return this; } @Override public AsyncRequestBuilder setHeader(final Header header) { super.setHeader(header); return this; } @Override public AsyncRequestBuilder setHeader(final String name, final String value) { super.setHeader(name, value); return this; } @Override public AsyncRequestBuilder setCharset(final Charset charset) { super.setCharset(charset); return this; } @Override public AsyncRequestBuilder addParameter(final NameValuePair nvp) { super.addParameter(nvp); return this; } @Override public AsyncRequestBuilder addParameter(final String name, final String value) { super.addParameter(name, value); return this; } @Override public AsyncRequestBuilder addParameters(final NameValuePair... nvps) { super.addParameters(nvps); return this; } @Override public AsyncRequestBuilder setAbsoluteRequestUri(final boolean absoluteRequestUri) { super.setAbsoluteRequestUri(absoluteRequestUri); return this; } public AsyncEntityProducer getEntity() { return entityProducer; } public AsyncRequestBuilder setEntity(final AsyncEntityProducer entityProducer) { this.entityProducer = entityProducer; return this; } public AsyncRequestBuilder setEntity(final String content, final ContentType contentType) { this.entityProducer = new BasicAsyncEntityProducer(content, contentType); return this; } public AsyncRequestBuilder setEntity(final String content) { this.entityProducer = new BasicAsyncEntityProducer(content); return this; } public AsyncRequestBuilder setEntity(final byte[] content, final ContentType contentType) { this.entityProducer = new BasicAsyncEntityProducer(content, contentType); return this; } @Override public AsyncRequestProducer build() { String path = getPath(); if (TextUtils.isEmpty(path)) { path = "/"; } AsyncEntityProducer entityProducerCopy = entityProducer; final String method = getMethod(); final List parameters = getParameters(); if (parameters != null && !parameters.isEmpty()) { final Charset charset = getCharset(); if (entityProducerCopy == null && (Method.POST.isSame(method) || Method.PUT.isSame(method))) { final String content = WWWFormCodec.format( parameters, charset != null ? charset : ContentType.APPLICATION_FORM_URLENCODED.getCharset()); entityProducerCopy = new StringAsyncEntityProducer( content, ContentType.APPLICATION_FORM_URLENCODED); } else { try { final URI uri = new URIBuilder(path) .setCharset(charset) .addParameters(parameters) .build(); path = uri.toASCIIString(); } catch (final URISyntaxException ex) { // should never happen } } } if (entityProducerCopy != null && Method.TRACE.isSame(method)) { throw new IllegalStateException(Method.TRACE + " requests may not include an entity"); } final BasicHttpRequest request = new BasicHttpRequest(method, getScheme(), getAuthority(), path); request.setVersion(getVersion()); request.setHeaders(getHeaders()); request.setAbsoluteRequestUri(isAbsoluteRequestUri()); return new BasicRequestProducer(request, entityProducerCopy); } @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("AsyncRequestBuilder [method="); builder.append(getMethod()); builder.append(", scheme="); builder.append(getScheme()); builder.append(", authority="); builder.append(getAuthority()); builder.append(", path="); builder.append(getPath()); builder.append(", parameters="); builder.append(getParameters()); builder.append(", headerGroup="); builder.append(Arrays.toString(getHeaders())); builder.append(", entity="); builder.append(entityProducer != null ? entityProducer.getClass() : null); builder.append("]"); return builder.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncResponseBuilder.java0100644 0000000 0000000 00000011637 00000250600 030011 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.util.Arrays; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.AsyncResponseProducer; import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityProducer; import org.apache.hc.core5.http.support.AbstractResponseBuilder; import org.apache.hc.core5.util.Args; /** * Builder for {@link AsyncResponseProducer} instances. * * @since 5.0 */ public class AsyncResponseBuilder extends AbstractResponseBuilder { private AsyncEntityProducer entityProducer; AsyncResponseBuilder(final int status) { super(status); } public static AsyncResponseBuilder create(final int status) { Args.checkRange(status, 100, 599, "HTTP status code"); return new AsyncResponseBuilder(status); } @Override public AsyncResponseBuilder setVersion(final ProtocolVersion version) { super.setVersion(version); return this; } @Override public AsyncResponseBuilder setHeaders(final Header... headers) { super.setHeaders(headers); return this; } @Override public AsyncResponseBuilder addHeader(final Header header) { super.addHeader(header); return this; } @Override public AsyncResponseBuilder addHeader(final String name, final String value) { super.addHeader(name, value); return this; } @Override public AsyncResponseBuilder removeHeader(final Header header) { super.removeHeader(header); return this; } @Override public AsyncResponseBuilder removeHeaders(final String name) { super.removeHeaders(name); return this; } @Override public AsyncResponseBuilder setHeader(final Header header) { super.setHeader(header); return this; } @Override public AsyncResponseBuilder setHeader(final String name, final String value) { super.setHeader(name, value); return this; } public AsyncEntityProducer getEntity() { return entityProducer; } public AsyncResponseBuilder setEntity(final AsyncEntityProducer entityProducer) { this.entityProducer = entityProducer; return this; } public AsyncResponseBuilder setEntity(final String content, final ContentType contentType) { this.entityProducer = new BasicAsyncEntityProducer(content, contentType); return this; } public AsyncResponseBuilder setEntity(final String content) { this.entityProducer = new BasicAsyncEntityProducer(content); return this; } public AsyncResponseBuilder setEntity(final byte[] content, final ContentType contentType) { this.entityProducer = new BasicAsyncEntityProducer(content, contentType); return this; } @Override public AsyncResponseProducer build() { final BasicHttpResponse response = new BasicHttpResponse(getStatus()); response.setVersion(getVersion()); response.setHeaders(getHeaders()); return new BasicResponseProducer(response, entityProducer); } @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("AsyncResponseBuilder [status="); builder.append(getStatus()); builder.append(", headerGroup="); builder.append(Arrays.toString(getHeaders())); builder.append(", entity="); builder.append(entityProducer != null ? entityProducer.getClass() : null); builder.append("]"); return builder.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncServerExpectationFilter.java0100644 0000000 0000000 00000007617 00000250600 031527 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.nio.AsyncDataConsumer; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.AsyncFilterChain; import org.apache.hc.core5.http.nio.AsyncFilterHandler; import org.apache.hc.core5.http.support.ExpectSupport; import org.apache.hc.core5.http.support.Expectation; import org.apache.hc.core5.http.protocol.HttpContext; /** * @since 5.0 */ @Contract(threading = ThreadingBehavior.STATELESS) public class AsyncServerExpectationFilter implements AsyncFilterHandler { protected boolean verify(final HttpRequest request, final HttpContext context) throws HttpException { return true; } protected AsyncEntityProducer generateResponseContent(final HttpResponse expectationFailed) throws HttpException { return null; } @Override public final AsyncDataConsumer handle( final HttpRequest request, final EntityDetails entityDetails, final HttpContext context, final AsyncFilterChain.ResponseTrigger responseTrigger, final AsyncFilterChain chain) throws HttpException, IOException { final Expectation expectation = ExpectSupport.parse(request, entityDetails); if (expectation == Expectation.CONTINUE) { final boolean verified = verify(request, context); if (verified) { responseTrigger.sendInformation(new BasicHttpResponse(HttpStatus.SC_CONTINUE)); } else { final HttpResponse expectationFailed = new BasicHttpResponse(HttpStatus.SC_EXPECTATION_FAILED); final AsyncEntityProducer responseContentProducer = generateResponseContent(expectationFailed); responseTrigger.submitResponse(expectationFailed, responseContentProducer); return null; } } else if (expectation == Expectation.UNKNOWN) { final HttpResponse expectationFailed = new BasicHttpResponse(HttpStatus.SC_EXPECTATION_FAILED); final AsyncEntityProducer responseContentProducer = generateResponseContent(expectationFailed); responseTrigger.submitResponse(expectationFailed, responseContentProducer); return null; } return chain.proceed(request, entityDetails, context, responseTrigger); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncServerFilterChainElement.java0100644 0000000 0000000 00000005270 00000250600 031571 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.io.IOException; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.nio.AsyncDataConsumer; import org.apache.hc.core5.http.nio.AsyncFilterChain; import org.apache.hc.core5.http.nio.AsyncFilterHandler; import org.apache.hc.core5.http.protocol.HttpContext; /** * An element in an asynchronous request processing chain. * * @since 5.0 */ public final class AsyncServerFilterChainElement { private final AsyncFilterHandler handler; private final AsyncServerFilterChainElement next; private final AsyncFilterChain filterChain; public AsyncServerFilterChainElement(final AsyncFilterHandler handler, final AsyncServerFilterChainElement next) { this.handler = handler; this.next = next; this.filterChain = next != null ? next::handle : null; } public AsyncDataConsumer handle( final HttpRequest request, final EntityDetails entityDetails, final HttpContext context, final AsyncFilterChain.ResponseTrigger responseTrigger) throws HttpException, IOException { return handler.handle(request, entityDetails, context, responseTrigger, filterChain); } @Override public String toString() { return "{" + "handler=" + handler.getClass() + ", next=" + (next != null ? next.handler.getClass() : "null") + '}'; } }././@LongLink0100644 0000000 0000000 00000000157 00000250600 011614 Lustar 0000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncServerFilterChainExchangeHandlerFactory.javahttpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncServerFilterChainExchangeHandlerFa0100644 0000000 0000000 00000017174 00000250600 032555 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.nio.AsyncDataConsumer; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.AsyncFilterChain; import org.apache.hc.core5.http.nio.AsyncPushProducer; import org.apache.hc.core5.http.nio.AsyncResponseProducer; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; /** * Factory for {@link AsyncServerExchangeHandler} instances that delegate request processing * to a {@link AsyncServerFilterChainElement}. * * @since 5.0 */ public final class AsyncServerFilterChainExchangeHandlerFactory implements HandlerFactory { private final AsyncServerFilterChainElement filterChain; private final Callback exceptionCallback; public AsyncServerFilterChainExchangeHandlerFactory(final AsyncServerFilterChainElement filterChain, final Callback exceptionCallback) { this.filterChain = Args.notNull(filterChain, "Filter chain"); this.exceptionCallback = exceptionCallback; } public AsyncServerFilterChainExchangeHandlerFactory(final AsyncServerFilterChainElement filterChain) { this(filterChain, null); } @Override public AsyncServerExchangeHandler create(final HttpRequest request, final HttpContext context) throws HttpException { return new AsyncServerExchangeHandler() { private final AtomicReference dataConsumerRef = new AtomicReference<>(); private final AtomicReference responseProducerRef = new AtomicReference<>(); @Override public void handleRequest( final HttpRequest request, final EntityDetails entityDetails, final ResponseChannel responseChannel, final HttpContext context) throws HttpException, IOException { dataConsumerRef.set(filterChain.handle(request, entityDetails, context, new AsyncFilterChain.ResponseTrigger() { @Override public void sendInformation( final HttpResponse response) throws HttpException, IOException { responseChannel.sendInformation(response, context); } @Override public void submitResponse( final HttpResponse response, final AsyncEntityProducer entityProducer) throws HttpException, IOException { final AsyncResponseProducer responseProducer = new BasicResponseProducer(response, entityProducer); responseProducerRef.set(responseProducer); responseProducer.sendResponse(responseChannel, context); } @Override public void pushPromise(final HttpRequest promise, final AsyncPushProducer responseProducer) throws HttpException, IOException { responseChannel.pushPromise(promise, responseProducer, context); } })); } @Override public void failed(final Exception cause) { if (exceptionCallback != null) { exceptionCallback.execute(cause); } final AsyncResponseProducer handler = responseProducerRef.get(); if (handler != null) { handler.failed(cause); } } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { final AsyncDataConsumer dataConsumer = dataConsumerRef.get(); if (dataConsumer != null) { dataConsumer.updateCapacity(capacityChannel); } else { capacityChannel.update(Integer.MAX_VALUE); } } @Override public void consume(final ByteBuffer src) throws IOException { final AsyncDataConsumer dataConsumer = dataConsumerRef.get(); if (dataConsumer != null) { dataConsumer.consume(src); } } @Override public void streamEnd(final List trailers) throws HttpException, IOException { final AsyncDataConsumer dataConsumer = dataConsumerRef.get(); if (dataConsumer != null) { dataConsumer.streamEnd(trailers); } } @Override public int available() { final AsyncResponseProducer responseProducer = responseProducerRef.get(); Asserts.notNull(responseProducer, "Response producer"); return responseProducer.available(); } @Override public void produce(final DataStreamChannel channel) throws IOException { final AsyncResponseProducer responseProducer = responseProducerRef.get(); Asserts.notNull(responseProducer, "Response producer"); responseProducer.produce(channel); } @Override public void releaseResources() { final AsyncDataConsumer dataConsumer = dataConsumerRef.getAndSet(null); if (dataConsumer != null) { dataConsumer.releaseResources(); } final AsyncResponseProducer responseProducer = responseProducerRef.getAndSet(null); if (responseProducer != null) { responseProducer.releaseResources(); } } }; } } ././@LongLink0100644 0000000 0000000 00000000147 00000250600 011613 Lustar 0000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicAsyncServerExpectationDecorator.javahttpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicAsyncServerExpectationDecorator.ja0100644 0000000 0000000 00000014460 00000250600 032631 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.nio.AsyncResponseProducer; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * {@link AsyncServerExchangeHandler} implementation that adds support * for the Expect-Continue handshake to an existing * {@link AsyncServerExchangeHandler}. * * @since 5.0 */ public class BasicAsyncServerExpectationDecorator implements AsyncServerExchangeHandler { private final AsyncServerExchangeHandler handler; private final Callback exceptionCallback; private final AtomicReference responseProducerRef; public BasicAsyncServerExpectationDecorator(final AsyncServerExchangeHandler handler, final Callback exceptionCallback) { this.handler = Args.notNull(handler, "Handler"); this.exceptionCallback = exceptionCallback; this.responseProducerRef = new AtomicReference<>(); } public BasicAsyncServerExpectationDecorator(final AsyncServerExchangeHandler handler) { this(handler, null); } protected AsyncResponseProducer verify( final HttpRequest request, final HttpContext context) throws IOException, HttpException { return null; } @Override public final void handleRequest( final HttpRequest request, final EntityDetails entityDetails, final ResponseChannel responseChannel, final HttpContext context) throws HttpException, IOException { if (entityDetails != null) { final Header h = request.getFirstHeader(HttpHeaders.EXPECT); if (h != null && HeaderElements.CONTINUE.equalsIgnoreCase(h.getValue())) { final AsyncResponseProducer producer = verify(request, context); if (producer != null) { responseProducerRef.set(producer); producer.sendResponse(responseChannel, context); return; } responseChannel.sendInformation(new BasicHttpResponse(HttpStatus.SC_CONTINUE), context); } } handler.handleRequest(request, entityDetails, responseChannel, context); } @Override public final void updateCapacity(final CapacityChannel capacityChannel) throws IOException { final AsyncResponseProducer responseProducer = responseProducerRef.get(); if (responseProducer == null) { handler.updateCapacity(capacityChannel); } else { capacityChannel.update(Integer.MAX_VALUE); } } @Override public final void consume(final ByteBuffer src) throws IOException { final AsyncResponseProducer responseProducer = responseProducerRef.get(); if (responseProducer == null) { handler.consume(src); } } @Override public final void streamEnd(final List trailers) throws HttpException, IOException { final AsyncResponseProducer responseProducer = responseProducerRef.get(); if (responseProducer == null) { handler.streamEnd(trailers); } } @Override public final int available() { final AsyncResponseProducer responseProducer = responseProducerRef.get(); return responseProducer == null ? handler.available() : responseProducer.available(); } @Override public final void produce(final DataStreamChannel channel) throws IOException { final AsyncResponseProducer responseProducer = responseProducerRef.get(); if (responseProducer == null) { handler.produce(channel); } else { responseProducer.produce(channel); } } @Override public final void failed(final Exception cause) { if (exceptionCallback != null) { exceptionCallback.execute(cause); } final AsyncResponseProducer dataProducer = responseProducerRef.get(); if (dataProducer == null) { handler.failed(cause); } else { dataProducer.failed(cause); } } @Override public final void releaseResources() { handler.releaseResources(); final AsyncResponseProducer dataProducer = responseProducerRef.getAndSet(null); if (dataProducer != null) { dataProducer.releaseResources(); } } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicClientExchangeHandler.java0100644 0000000 0000000 00000017037 00000250600 031027 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; import org.apache.hc.core5.http.nio.AsyncRequestProducer; import org.apache.hc.core5.http.nio.AsyncResponseConsumer; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.RequestChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * Basic {@link AsyncClientExchangeHandler} implementation that makes use * of {@link AsyncRequestProducer} to generate request message * and {@link AsyncResponseConsumer} to process the response message returned by the server. * * @param The result type. * @since 5.0 */ public final class BasicClientExchangeHandler implements AsyncClientExchangeHandler { private final AsyncRequestProducer requestProducer; private final AsyncResponseConsumer responseConsumer; private final AtomicBoolean completed; private final AtomicBoolean outputTerminated; private final AtomicBoolean inputTerminated; private final FutureCallback resultCallback; public BasicClientExchangeHandler( final AsyncRequestProducer requestProducer, final AsyncResponseConsumer responseConsumer, final FutureCallback resultCallback) { this.requestProducer = Args.notNull(requestProducer, "Request producer"); this.responseConsumer = Args.notNull(responseConsumer, "Response consumer"); this.completed = new AtomicBoolean(); this.resultCallback = resultCallback; this.outputTerminated = new AtomicBoolean(); this.inputTerminated = new AtomicBoolean(); } @Override public void produceRequest(final RequestChannel requestChannel, final HttpContext httpContext) throws HttpException, IOException { requestProducer.sendRequest(requestChannel, httpContext); } @Override public int available() { return requestProducer.available(); } @Override public void produce(final DataStreamChannel channel) throws IOException { if (outputTerminated.get()) { channel.endStream(); return; } requestProducer.produce(channel); } @Override public void consumeInformation(final HttpResponse response, final HttpContext httpContext) throws HttpException, IOException { responseConsumer.informationResponse(response, httpContext); } @Override public void consumeResponse(final HttpResponse response, final EntityDetails entityDetails, final HttpContext httpContext) throws HttpException, IOException { if (response.getCode() >= HttpStatus.SC_CLIENT_ERROR) { releaseRequestProducer(); } responseConsumer.consumeResponse(response, entityDetails, httpContext, new FutureCallback() { @Override public void completed(final T result) { completedInternal(result); } @Override public void failed(final Exception ex) { failedInternal(ex); } @Override public void cancelled() { cancelledInternal(); } }); } @Override public void cancel() { cancelledInternal(); } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { responseConsumer.updateCapacity(capacityChannel); } @Override public void consume(final ByteBuffer src) throws IOException { responseConsumer.consume(src); } @Override public void streamEnd(final List trailers) throws HttpException, IOException { responseConsumer.streamEnd(trailers); } @Override public void failed(final Exception cause) { try { if (inputTerminated.compareAndSet(false, true)) { responseConsumer.failed(cause); responseConsumer.releaseResources(); } if (outputTerminated.compareAndSet(false, true)) { requestProducer.failed(cause); requestProducer.releaseResources(); } } finally { failedInternal(cause); } } private void completedInternal(final T result) { if (completed.compareAndSet(false, true)) { try { if (resultCallback != null) { resultCallback.completed(result); } } finally { releaseResourcesInternal(); } } } private void failedInternal(final Exception ex) { if (completed.compareAndSet(false, true)) { try { if (resultCallback != null) { resultCallback.failed(ex); } } finally { releaseResourcesInternal(); } } } private void cancelledInternal() { if (completed.compareAndSet(false, true)) { try { if (resultCallback != null) { resultCallback.cancelled(); } } finally { releaseResourcesInternal(); } } } private void releaseResponseConsumer() { if (inputTerminated.compareAndSet(false, true)) { responseConsumer.releaseResources(); } } private void releaseRequestProducer() { if (outputTerminated.compareAndSet(false, true)) { requestProducer.releaseResources(); } } private void releaseResourcesInternal() { releaseRequestProducer(); releaseResponseConsumer(); } @Override public void releaseResources() { // Note even though the message exchange has been fully // completed on the transport level, the response // consumer may still be busy consuming and digesting // the response message releaseRequestProducer(); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicPushProducer.java0100644 0000000 0000000 00000006521 00000250600 027267 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.io.IOException; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.AsyncPushProducer; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * Basic implementation of {@link AsyncPushProducer} that produces one fixed response * and relies on a {@link AsyncEntityProducer} to generate response entity stream. * * @since 5.0 */ public class BasicPushProducer implements AsyncPushProducer { private final HttpResponse response; private final AsyncEntityProducer dataProducer; public BasicPushProducer(final HttpResponse response, final AsyncEntityProducer dataProducer) { this.response = Args.notNull(response, "Response"); this.dataProducer = Args.notNull(dataProducer, "Entity producer"); } public BasicPushProducer(final int code, final AsyncEntityProducer dataProducer) { this(new BasicHttpResponse(code), dataProducer); } public BasicPushProducer(final AsyncEntityProducer dataProducer) { this(HttpStatus.SC_OK, dataProducer); } @Override public void produceResponse(final ResponseChannel channel, final HttpContext httpContext) throws HttpException, IOException { channel.sendResponse(response, dataProducer, httpContext); } @Override public int available() { return dataProducer != null ? dataProducer.available() : 0; } @Override public void produce(final DataStreamChannel channel) throws IOException { if (dataProducer != null) { dataProducer.produce(channel); } } @Override public void failed(final Exception cause) { releaseResources(); } @Override public void releaseResources() { if (dataProducer != null) { dataProducer.releaseResources(); } } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicRequestConsumer.java0100644 0000000 0000000 00000012214 00000250600 030004 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.concurrent.CallbackContribution; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.nio.AsyncEntityConsumer; import org.apache.hc.core5.http.nio.AsyncRequestConsumer; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * Basic implementation of {@link AsyncRequestConsumer} that represents the request message as * a {@link Message} and relies on a {@link AsyncEntityConsumer} to process request entity stream. * * @param The message body type. * @since 5.0 */ public class BasicRequestConsumer implements AsyncRequestConsumer> { private final Supplier> dataConsumerSupplier; private final AtomicReference> dataConsumerRef; public BasicRequestConsumer(final Supplier> dataConsumerSupplier) { this.dataConsumerSupplier = Args.notNull(dataConsumerSupplier, "Data consumer supplier"); this.dataConsumerRef = new AtomicReference<>(); } public BasicRequestConsumer(final AsyncEntityConsumer dataConsumer) { this(() -> dataConsumer); } @Override public void consumeRequest( final HttpRequest request, final EntityDetails entityDetails, final HttpContext httpContext, final FutureCallback> resultCallback) throws HttpException, IOException { Args.notNull(request, "Request"); if (entityDetails != null) { final AsyncEntityConsumer dataConsumer = dataConsumerSupplier.get(); if (dataConsumer == null) { throw new HttpException("Supplied data consumer is null"); } dataConsumerRef.set(dataConsumer); dataConsumer.streamStart(entityDetails, new CallbackContribution(resultCallback) { @Override public void completed(final T body) { if (resultCallback != null) { resultCallback.completed(new Message<>(request, body)); } } }); } else { if (resultCallback != null) { resultCallback.completed(new Message<>(request)); } } } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { final AsyncEntityConsumer dataConsumer = dataConsumerRef.get(); if (dataConsumer != null) { dataConsumer.updateCapacity(capacityChannel); } else { capacityChannel.update(Integer.MAX_VALUE); } } @Override public void consume(final ByteBuffer src) throws IOException { final AsyncEntityConsumer dataConsumer = dataConsumerRef.get(); if (dataConsumer != null) { dataConsumer.consume(src); } } @Override public void streamEnd(final List trailers) throws HttpException, IOException { final AsyncEntityConsumer dataConsumer = dataConsumerRef.get(); if (dataConsumer != null) { dataConsumer.streamEnd(trailers); } } @Override public void failed(final Exception cause) { releaseResources(); } @Override public void releaseResources() { final AsyncEntityConsumer dataConsumer = dataConsumerRef.getAndSet(null); if (dataConsumer != null) { dataConsumer.releaseResources(); } } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicRequestProducer.java0100644 0000000 0000000 00000011161 00000250600 027774 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.io.IOException; import java.net.URI; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.AsyncRequestProducer; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.RequestChannel; import org.apache.hc.core5.http.protocol.HttpContext; /** * Basic implementation of {@link AsyncRequestProducer} that produces one fixed request * and relies on a {@link AsyncEntityProducer} to generate request entity stream. * * @since 5.0 */ public class BasicRequestProducer implements AsyncRequestProducer { private final HttpRequest request; private final AsyncEntityProducer dataProducer; public BasicRequestProducer(final HttpRequest request, final AsyncEntityProducer dataProducer) { this.request = request; this.dataProducer = dataProducer; } public BasicRequestProducer(final String method, final HttpHost host, final String path, final AsyncEntityProducer dataProducer) { this(new BasicHttpRequest(method, host, path), dataProducer); } public BasicRequestProducer(final String method, final HttpHost host, final String path) { this(method, host, path, null); } public BasicRequestProducer(final String method, final URI requestUri, final AsyncEntityProducer dataProducer) { this(new BasicHttpRequest(method, requestUri), dataProducer); } public BasicRequestProducer(final String method, final URI requestUri) { this(method, requestUri, null); } public BasicRequestProducer(final Method method, final HttpHost host, final String path, final AsyncEntityProducer dataProducer) { this(new BasicHttpRequest(method, host, path), dataProducer); } public BasicRequestProducer(final Method method, final HttpHost host, final String path) { this(method, host, path, null); } public BasicRequestProducer(final Method method, final URI requestUri, final AsyncEntityProducer dataProducer) { this(new BasicHttpRequest(method, requestUri), dataProducer); } public BasicRequestProducer(final Method method, final URI requestUri) { this(method, requestUri, null); } @Override public void sendRequest(final RequestChannel requestChannel, final HttpContext httpContext) throws HttpException, IOException { requestChannel.sendRequest(request, dataProducer, httpContext); } @Override public int available() { return dataProducer != null ? dataProducer.available() : 0; } @Override public void produce(final DataStreamChannel channel) throws IOException { if (dataProducer != null) { dataProducer.produce(channel); } } @Override public boolean isRepeatable() { return dataProducer == null || dataProducer.isRepeatable(); } @Override public void failed(final Exception cause) { try { if (dataProducer != null) { dataProducer.failed(cause); } } finally { releaseResources(); } } @Override public void releaseResources() { if (dataProducer != null) { dataProducer.releaseResources(); } } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicResponseConsumer.java0100644 0000000 0000000 00000012713 00000250600 030156 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.concurrent.CallbackContribution; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.nio.AsyncEntityConsumer; import org.apache.hc.core5.http.nio.AsyncResponseConsumer; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * Basic implementation of {@link AsyncResponseConsumer} that represents response message as * a {@link Message} and relies on a {@link AsyncEntityConsumer} to process response entity * stream. * * @param The message body type. * @since 5.0 */ public class BasicResponseConsumer implements AsyncResponseConsumer> { private final Supplier> dataConsumerSupplier; private final AtomicReference> dataConsumerRef; public BasicResponseConsumer(final Supplier> dataConsumerSupplier) { this.dataConsumerSupplier = Args.notNull(dataConsumerSupplier, "Data consumer supplier"); this.dataConsumerRef = new AtomicReference<>(); } public BasicResponseConsumer(final AsyncEntityConsumer dataConsumer) { this(() -> dataConsumer); } @Override public void consumeResponse( final HttpResponse response, final EntityDetails entityDetails, final HttpContext httpContext, final FutureCallback> resultCallback) throws HttpException, IOException { Args.notNull(response, "Response"); if (entityDetails != null) { final AsyncEntityConsumer dataConsumer = dataConsumerSupplier.get(); if (dataConsumer == null) { throw new HttpException("Supplied data consumer is null"); } dataConsumerRef.set(dataConsumer); dataConsumer.streamStart(entityDetails, new CallbackContribution(resultCallback) { @Override public void completed(final T body) { if (resultCallback != null) { resultCallback.completed(new Message<>(response, body)); } } }); } else { if (resultCallback != null) { resultCallback.completed(new Message<>(response)); } } } @Override public void informationResponse(final HttpResponse response, final HttpContext httpContext) throws HttpException, IOException { } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { final AsyncEntityConsumer dataConsumer = dataConsumerRef.get(); if (dataConsumer != null) { dataConsumer.updateCapacity(capacityChannel); } else { capacityChannel.update(Integer.MAX_VALUE); } } @Override public void consume(final ByteBuffer src) throws IOException { final AsyncEntityConsumer dataConsumer = dataConsumerRef.get(); if (dataConsumer != null) { dataConsumer.consume(src); } } @Override public void streamEnd(final List trailers) throws HttpException, IOException { final AsyncEntityConsumer dataConsumer = dataConsumerRef.get(); if (dataConsumer != null) { dataConsumer.streamEnd(trailers); } } @Override public void failed(final Exception cause) { final AsyncEntityConsumer dataConsumer = dataConsumerRef.get(); if (dataConsumer != null) { dataConsumer.failed(cause); } releaseResources(); } @Override public void releaseResources() { final AsyncEntityConsumer dataConsumer = dataConsumerRef.getAndSet(null); if (dataConsumer != null) { dataConsumer.releaseResources(); } } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicResponseProducer.java0100644 0000000 0000000 00000010523 00000250600 030143 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.io.IOException; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.AsyncResponseProducer; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * Basic implementation of {@link AsyncResponseProducer} that produces one fixed response * and relies on a {@link AsyncEntityProducer} to generate response entity stream. * * @since 5.0 */ public class BasicResponseProducer implements AsyncResponseProducer { private final HttpResponse response; private final AsyncEntityProducer dataProducer; public BasicResponseProducer(final HttpResponse response, final AsyncEntityProducer dataProducer) { this.response = Args.notNull(response, "Response"); this.dataProducer = dataProducer; } public BasicResponseProducer(final HttpResponse response) { this.response = Args.notNull(response, "Response"); this.dataProducer = null; } public BasicResponseProducer(final int code, final AsyncEntityProducer dataProducer) { this(new BasicHttpResponse(code), dataProducer); } public BasicResponseProducer(final HttpResponse response, final String message, final ContentType contentType) { this(response, AsyncEntityProducers.create(message, contentType)); } public BasicResponseProducer(final HttpResponse response, final String message) { this(response, message, ContentType.TEXT_PLAIN); } public BasicResponseProducer(final int code, final String message, final ContentType contentType) { this(new BasicHttpResponse(code), message, contentType); } public BasicResponseProducer(final int code, final String message) { this(new BasicHttpResponse(code), message); } public BasicResponseProducer(final AsyncEntityProducer dataProducer) { this(HttpStatus.SC_OK, dataProducer); } @Override public void sendResponse(final ResponseChannel responseChannel, final HttpContext httpContext) throws HttpException, IOException { responseChannel.sendResponse(response, dataProducer, httpContext); } @Override public int available() { return dataProducer != null ? dataProducer.available() : 0; } @Override public void produce(final DataStreamChannel channel) throws IOException { if (dataProducer != null) { dataProducer.produce(channel); } } @Override public void failed(final Exception cause) { if (dataProducer != null) { dataProducer.failed(cause); } releaseResources(); } @Override public void releaseResources() { if (dataProducer != null) { dataProducer.releaseResources(); } } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicServerExchangeHandler.java0100644 0000000 0000000 00000005355 00000250600 031057 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.io.IOException; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.nio.AsyncRequestConsumer; import org.apache.hc.core5.http.nio.AsyncServerRequestHandler; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * Basic {@link AbstractServerExchangeHandler} implementation that delegates * request processing and response generation to a {@link AsyncServerRequestHandler}. * * @param the type of request messages. * @since 5.0 */ public class BasicServerExchangeHandler extends AbstractServerExchangeHandler { private final AsyncServerRequestHandler requestHandler; public BasicServerExchangeHandler(final AsyncServerRequestHandler requestHandler) { super(); this.requestHandler = Args.notNull(requestHandler, "Response handler"); } @Override protected AsyncRequestConsumer supplyConsumer( final HttpRequest request, final EntityDetails entityDetails, final HttpContext context) throws HttpException { return requestHandler.prepare(request, entityDetails, context); } @Override protected void handle( final T requestMessage, final AsyncServerRequestHandler.ResponseTrigger responseTrigger, final HttpContext context) throws HttpException, IOException { requestHandler.handle(requestMessage, responseTrigger, context); } } ././@LongLink0100644 0000000 0000000 00000000155 00000250600 011612 Lustar 0000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/DefaultAsyncResponseExchangeHandlerFactory.javahttpcore5/src/main/java/org/apache/hc/core5/http/nio/support/DefaultAsyncResponseExchangeHandlerFact0100644 0000000 0000000 00000007327 00000250600 032627 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestMapper; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.MisdirectedRequestException; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * Factory for {@link AsyncServerExchangeHandler} instances that make use * of {@link HttpRequestMapper} to dispatch * the request to a particular {@link AsyncServerExchangeHandler} for processing. * * @since 5.0 */ public final class DefaultAsyncResponseExchangeHandlerFactory implements HandlerFactory { private final HttpRequestMapper> mapper; private final Decorator decorator; public DefaultAsyncResponseExchangeHandlerFactory( final HttpRequestMapper> mapper, final Decorator decorator) { this.mapper = Args.notNull(mapper, "Request handler mapper"); this.decorator = decorator; } public DefaultAsyncResponseExchangeHandlerFactory(final HttpRequestMapper> mapper) { this(mapper, null); } private AsyncServerExchangeHandler createHandler(final HttpRequest request, final HttpContext context) throws HttpException { try { final Supplier supplier = mapper.resolve(request, context); return supplier != null ? supplier.get() : new ImmediateResponseExchangeHandler(HttpStatus.SC_NOT_FOUND, "Resource not found"); } catch (final MisdirectedRequestException ex) { return new ImmediateResponseExchangeHandler(HttpStatus.SC_MISDIRECTED_REQUEST, "Not authoritative"); } } @Override public AsyncServerExchangeHandler create(final HttpRequest request, final HttpContext context) throws HttpException { final AsyncServerExchangeHandler handler = createHandler(request, context); if (handler != null) { return decorator != null ? decorator.decorate(handler) : handler; } return null; } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/ImmediateResponseExchangeHandler.java0100644 0000000 0000000 00000010054 00000250600 032254 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.nio.AsyncResponseProducer; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * {@link AsyncServerExchangeHandler} implementation that immediately responds * with a predefined response generated by a {@link AsyncResponseProducer} and * ignores any entity content enclosed in the request message. * * @since 5.0 */ public final class ImmediateResponseExchangeHandler implements AsyncServerExchangeHandler { private final AsyncResponseProducer responseProducer; public ImmediateResponseExchangeHandler(final AsyncResponseProducer responseProducer) { this.responseProducer = Args.notNull(responseProducer, "Response producer"); } public ImmediateResponseExchangeHandler(final HttpResponse response, final String message) { this(new BasicResponseProducer(response, AsyncEntityProducers.create(message))); } public ImmediateResponseExchangeHandler(final int status, final String message) { this(new BasicHttpResponse(status), message); } @Override public void handleRequest( final HttpRequest request, final EntityDetails entityDetails, final ResponseChannel responseChannel, final HttpContext context) throws HttpException, IOException { responseProducer.sendResponse(responseChannel, context); } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { capacityChannel.update(Integer.MAX_VALUE); } @Override public void consume(final ByteBuffer src) throws IOException { } @Override public void streamEnd(final List trailers) throws HttpException, IOException { } @Override public int available() { return responseProducer.available(); } @Override public void produce(final DataStreamChannel channel) throws IOException { responseProducer.produce(channel); } @Override public void failed(final Exception cause) { responseProducer.failed(cause); releaseResources(); } @Override public void releaseResources() { responseProducer.releaseResources(); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/TerminalAsyncServerFilter.java0100644 0000000 0000000 00000014614 00000250600 031012 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support; import java.io.IOException; import java.util.Set; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.nio.AsyncDataConsumer; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.AsyncFilterChain; import org.apache.hc.core5.http.nio.AsyncFilterHandler; import org.apache.hc.core5.http.nio.AsyncPushProducer; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * {@link AsyncFilterHandler} implementation represents a terminal handler * in an asynchronous request processing pipeline that makes use of {@link HandlerFactory} * to dispatch the request to a particular {@link AsyncServerExchangeHandler}. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.STATELESS) public final class TerminalAsyncServerFilter implements AsyncFilterHandler { final private HandlerFactory handlerFactory; public TerminalAsyncServerFilter(final HandlerFactory handlerFactory) { this.handlerFactory = Args.notNull(handlerFactory, "Handler factory"); } @Override public AsyncDataConsumer handle( final HttpRequest request, final EntityDetails entityDetails, final HttpContext context, final AsyncFilterChain.ResponseTrigger responseTrigger, final AsyncFilterChain chain) throws HttpException, IOException { final AsyncServerExchangeHandler exchangeHandler = handlerFactory.create(request, context); if (exchangeHandler != null) { exchangeHandler.handleRequest(request, entityDetails, new ResponseChannel() { @Override public void sendInformation(final HttpResponse response, final HttpContext httpContext) throws HttpException, IOException { responseTrigger.sendInformation(response); } @Override public void sendResponse(final HttpResponse response, final EntityDetails entityDetails, final HttpContext httpContext) throws HttpException, IOException { responseTrigger.submitResponse(response, entityDetails != null ? new AsyncEntityProducer() { @Override public void failed(final Exception cause) { exchangeHandler.failed(cause); } @Override public boolean isRepeatable() { return false; } @Override public long getContentLength() { return entityDetails.getContentLength(); } @Override public String getContentType() { return entityDetails.getContentType(); } @Override public String getContentEncoding() { return entityDetails.getContentEncoding(); } @Override public boolean isChunked() { return entityDetails.isChunked(); } @Override public Set getTrailerNames() { return entityDetails.getTrailerNames(); } @Override public int available() { return exchangeHandler.available(); } @Override public void produce(final DataStreamChannel channel) throws IOException { exchangeHandler.produce(channel); } @Override public void releaseResources() { exchangeHandler.releaseResources(); } } : null); } @Override public void pushPromise(final HttpRequest promise, final AsyncPushProducer pushProducer, final HttpContext httpContext) throws HttpException, IOException { responseTrigger.pushPromise(promise, pushProducer); } }, context); return exchangeHandler; } responseTrigger.submitResponse(new BasicHttpResponse(HttpStatus.SC_NOT_FOUND), AsyncEntityProducers.create("Not found")); return null; } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/package-info.java0100644 0000000 0000000 00000002412 00000250600 026221 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Support classes for the asynchronous I/O model. */ package org.apache.hc.core5.http.nio.support; httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/0040755 0000000 0000000 00000000000 00000250600 024457 5ustar000000000 0000000 ././@LongLink0100644 0000000 0000000 00000000150 00000250600 011605 Lustar 0000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/AbstractClassicEntityConsumer.javahttpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/AbstractClassicEntityConsumer.j0100644 0000000 0000000 00000012201 00000250600 032601 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support.classic; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.charset.UnsupportedCharsetException; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.nio.AsyncEntityConsumer; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.util.Args; /** * {@link AsyncEntityConsumer} implementation that acts as a compatibility * layer for classic {@link InputStream} based interfaces. Blocking input * processing is executed through an {@link Executor}. * * @param entity representation. * * @since 5.0 * * @deprecated Use {@link ClassicToAsyncResponseConsumer}. */ @Deprecated public abstract class AbstractClassicEntityConsumer implements AsyncEntityConsumer { private enum State { IDLE, ACTIVE, COMPLETED } private final Executor executor; private final SharedInputBuffer buffer; private final AtomicReference state; private final AtomicReference resultRef; private final AtomicReference exceptionRef; public AbstractClassicEntityConsumer(final int initialBufferSize, final Executor executor) { this.executor = Args.notNull(executor, "Executor"); this.buffer = new SharedInputBuffer(initialBufferSize); this.state = new AtomicReference<>(State.IDLE); this.resultRef = new AtomicReference<>(); this.exceptionRef = new AtomicReference<>(); } /** * Processes entity data from the given stream. * * @param contentType the entity content type * @param inputStream the input stream * @return the result of entity processing. * @throws IOException in case of an I/O error. */ protected abstract T consumeData(ContentType contentType, InputStream inputStream) throws IOException; @Override public final void updateCapacity(final CapacityChannel capacityChannel) throws IOException { buffer.updateCapacity(capacityChannel); } @Override public final void streamStart(final EntityDetails entityDetails, final FutureCallback resultCallback) throws HttpException, IOException { final ContentType contentType; try { contentType = ContentType.parse(entityDetails.getContentType()); } catch (final UnsupportedCharsetException ex) { throw new UnsupportedEncodingException(ex.getMessage()); } if (state.compareAndSet(State.IDLE, State.ACTIVE)) { executor.execute(() -> { try { final T result = consumeData(contentType, new ContentInputStream(buffer)); resultRef.set(result); resultCallback.completed(result); } catch (final Exception ex) { buffer.abort(); resultCallback.failed(ex); } finally { state.set(State.COMPLETED); } }); } } @Override public final void consume(final ByteBuffer src) throws IOException { buffer.fill(src); } @Override public final void streamEnd(final List trailers) throws HttpException, IOException { buffer.markEndStream(); } @Override public final void failed(final Exception cause) { if (exceptionRef.compareAndSet(null, cause)) { releaseResources(); } } public final Exception getException() { return exceptionRef.get(); } @Override public final T getContent() { return resultRef.get(); } @Override public void releaseResources() { } } ././@LongLink0100644 0000000 0000000 00000000150 00000250600 011605 Lustar 0000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/AbstractClassicEntityProducer.javahttpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/AbstractClassicEntityProducer.j0100644 0000000 0000000 00000010730 00000250600 032576 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support.classic; import java.io.IOException; import java.io.OutputStream; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.util.Args; /** * {@link AsyncEntityProducer} implementation that acts as a compatibility * layer for classic {@link OutputStream} based interfaces. Blocking output * processing is executed through an {@link Executor}. * * @since 5.0 * * @deprecated Use {@link ClassicToAsyncRequestProducer}. */ @Deprecated public abstract class AbstractClassicEntityProducer implements AsyncEntityProducer { private enum State { IDLE, ACTIVE, COMPLETED } private final SharedOutputBuffer buffer; private final ContentType contentType; private final Executor executor; private final AtomicReference state; private final AtomicReference exception; public AbstractClassicEntityProducer(final int initialBufferSize, final ContentType contentType, final Executor executor) { this.buffer = new SharedOutputBuffer(initialBufferSize); this.contentType = contentType; this.executor = Args.notNull(executor, "Executor"); this.state = new AtomicReference<>(State.IDLE); this.exception = new AtomicReference<>(); } /** * Writes out entity data into the given stream. * * @param contentType the entity content type * @param outputStream the output stream * @throws IOException in case of an I/O error. */ protected abstract void produceData(ContentType contentType, OutputStream outputStream) throws IOException; @Override public final boolean isRepeatable() { return false; } @Override public final int available() { return buffer.length(); } @Override public final void produce(final DataStreamChannel channel) throws IOException { if (state.compareAndSet(State.IDLE, State.ACTIVE)) { executor.execute(() -> { try { produceData(contentType, new ContentOutputStream(buffer)); buffer.writeCompleted(); } catch (final Exception ex) { buffer.abort(); } finally { state.set(State.COMPLETED); } }); } buffer.flush(channel); } @Override public final long getContentLength() { return -1; } @Override public final String getContentType() { return Objects.toString(contentType, null); } @Override public String getContentEncoding() { return null; } @Override public final boolean isChunked() { return false; } @Override public final Set getTrailerNames() { return null; } @Override public final void failed(final Exception cause) { if (exception.compareAndSet(null, cause)) { releaseResources(); } } public final Exception getException() { return exception.get(); } @Override public void releaseResources() { } } ././@LongLink0100644 0000000 0000000 00000000157 00000250600 011614 Lustar 0000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/AbstractClassicServerExchangeHandler.javahttpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/AbstractClassicServerExchangeHa0100644 0000000 0000000 00000025340 00000250600 032553 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support.classic; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.message.HttpResponseWrapper; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.io.Closer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; /** * {@link AsyncServerExchangeHandler} implementation that acts as a compatibility * layer for classic {@link InputStream} / {@link OutputStream} based interfaces. * Blocking input / output processing is executed through an {@link Executor}. * * @since 5.0 * * @deprecated Use {@link ClassicToAsyncServerExchangeHandler}. */ @Deprecated public abstract class AbstractClassicServerExchangeHandler implements AsyncServerExchangeHandler { private enum State { IDLE, ACTIVE, COMPLETED } private final int initialBufferSize; private final Executor executor; private final AtomicReference state; private final AtomicReference exception; private volatile SharedInputBuffer inputBuffer; private volatile SharedOutputBuffer outputBuffer; public AbstractClassicServerExchangeHandler(final int initialBufferSize, final Executor executor) { this.initialBufferSize = Args.positive(initialBufferSize, "Initial buffer size"); this.executor = Args.notNull(executor, "Executor"); this.exception = new AtomicReference<>(); this.state = new AtomicReference<>(State.IDLE); } /** * Handles an incoming request optionally reading its entity content form the given input stream * and generates a response optionally writing out its entity content into the given output stream. * * @param request the incoming request * @param requestStream the request stream if the request encloses an entity, * {@code null} otherwise. * @param response the outgoing response. * @param responseStream the response entity output stream. * @param context the actual execution context. * @throws IOException in case of an I/O error. * @throws HttpException in case of an HTTP protocol violation. */ protected abstract void handle( HttpRequest request, InputStream requestStream, HttpResponse response, OutputStream responseStream, HttpContext context) throws IOException, HttpException; public Exception getException() { return exception.get(); } @Override public final void handleRequest( final HttpRequest request, final EntityDetails entityDetails, final ResponseChannel responseChannel, final HttpContext context) throws HttpException, IOException { final AtomicBoolean responseCommitted = new AtomicBoolean(); final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK); final HttpResponse responseWrapper = new HttpResponseWrapper(response) { private void ensureNotCommitted() { Asserts.check(!responseCommitted.get(), "Response already committed"); } @Override public void addHeader(final String name, final Object value) { ensureNotCommitted(); super.addHeader(name, value); } @Override public void setHeader(final String name, final Object value) { ensureNotCommitted(); super.setHeader(name, value); } @Override public void setVersion(final ProtocolVersion version) { ensureNotCommitted(); super.setVersion(version); } @Override public void setCode(final int code) { ensureNotCommitted(); super.setCode(code); } @Override public void setReasonPhrase(final String reason) { ensureNotCommitted(); super.setReasonPhrase(reason); } @Override public void setLocale(final Locale locale) { ensureNotCommitted(); super.setLocale(locale); } }; final InputStream inputStream; if (entityDetails != null) { inputBuffer = new SharedInputBuffer(initialBufferSize); inputStream = new ContentInputStream(inputBuffer); } else { inputStream = null; } outputBuffer = new SharedOutputBuffer(initialBufferSize); final OutputStream outputStream = new ContentOutputStream(outputBuffer) { private void triggerResponse() throws IOException { try { if (responseCommitted.compareAndSet(false, true)) { responseChannel.sendResponse(response, new EntityDetails() { @Override public long getContentLength() { return -1; } @Override public String getContentType() { final Header h = response.getFirstHeader(HttpHeaders.CONTENT_TYPE); return h != null ? h.getValue() : null; } @Override public String getContentEncoding() { final Header h = response.getFirstHeader(HttpHeaders.CONTENT_ENCODING); return h != null ? h.getValue() : null; } @Override public boolean isChunked() { return false; } @Override public Set getTrailerNames() { return Collections.emptySet(); } }, context); } } catch (final HttpException ex) { throw new IOException(ex.getMessage(), ex); } } @Override public void close() throws IOException { triggerResponse(); super.close(); } @Override public void write(final byte[] b, final int off, final int len) throws IOException { triggerResponse(); super.write(b, off, len); } @Override public void write(final byte[] b) throws IOException { triggerResponse(); super.write(b); } @Override public void write(final int b) throws IOException { triggerResponse(); super.write(b); } }; if (state.compareAndSet(State.IDLE, State.ACTIVE)) { executor.execute(() -> { try { handle(request, inputStream, responseWrapper, outputStream, context); Closer.close(inputStream); outputStream.close(); } catch (final Exception ex) { exception.compareAndSet(null, ex); if (inputBuffer != null) { inputBuffer.abort(); } outputBuffer.abort(); } finally { state.set(State.COMPLETED); } }); } } @Override public final void updateCapacity(final CapacityChannel capacityChannel) throws IOException { if (inputBuffer != null) { inputBuffer.updateCapacity(capacityChannel); } } @Override public final void consume(final ByteBuffer src) throws IOException { Asserts.notNull(inputBuffer, "Input buffer"); inputBuffer.fill(src); } @Override public final void streamEnd(final List trailers) throws HttpException, IOException { Asserts.notNull(inputBuffer, "Input buffer"); inputBuffer.markEndStream(); } @Override public final int available() { Asserts.notNull(outputBuffer, "Output buffer"); return outputBuffer.length(); } @Override public final void produce(final DataStreamChannel channel) throws IOException { Asserts.notNull(outputBuffer, "Output buffer"); outputBuffer.flush(channel); } @Override public final void failed(final Exception cause) { exception.compareAndSet(null, cause); releaseResources(); } @Override public void releaseResources() { } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/AbstractSharedBuffer.java0100644 0000000 0000000 00000006225 00000250600 031350 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support.classic; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.impl.nio.ExpandableBuffer; import org.apache.hc.core5.util.Args; /** * @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE) abstract class AbstractSharedBuffer extends ExpandableBuffer { final ReentrantLock lock; final Condition condition; volatile boolean endStream; volatile boolean aborted; public AbstractSharedBuffer(final ReentrantLock lock, final int initialBufferSize) { super(initialBufferSize); this.lock = Args.notNull(lock, "Lock"); this.condition = lock.newCondition(); } @Override public boolean hasData() { lock.lock(); try { return super.hasData(); } finally { lock.unlock(); } } @Override public int capacity() { lock.lock(); try { return super.capacity(); } finally { lock.unlock(); } } @Override public int length() { lock.lock(); try { return super.length(); } finally { lock.unlock(); } } public void abort() { lock.lock(); try { endStream = true; aborted = true; condition.signalAll(); } finally { lock.unlock(); } } public void reset() { if (aborted) { return; } lock.lock(); try { setInputMode(); buffer().clear(); endStream = false; } finally { lock.unlock(); } } public boolean isEndStream() { lock.lock(); try { return endStream && !super.hasData(); } finally { lock.unlock(); } } } ././@LongLink0100644 0000000 0000000 00000000150 00000250600 011605 Lustar 0000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ClassicToAsyncRequestProducer.javahttpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ClassicToAsyncRequestProducer.j0100644 0000000 0000000 00000014771 00000250600 032600 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support.classic; import java.io.IOException; import java.io.InterruptedIOException; import java.io.OutputStream; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.nio.AsyncRequestProducer; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.RequestChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; import org.apache.hc.core5.util.Timeout; /** * @since 5.4 */ @Experimental public class ClassicToAsyncRequestProducer implements AsyncRequestProducer { private final ClassicHttpRequest request; private final int initialBufferSize; private final Timeout timeout; private final CountDownLatch countDownLatch; private final AtomicReference bufferRef; private final AtomicReference exceptionRef; private volatile boolean repeatable; public interface IORunnable { void execute() throws IOException; } public ClassicToAsyncRequestProducer(final ClassicHttpRequest request, final int initialBufferSize, final Timeout timeout) { this.request = Args.notNull(request, "HTTP request"); this.initialBufferSize = Args.positive(initialBufferSize, "Initial buffer size"); this.timeout = timeout; this.countDownLatch = new CountDownLatch(1); this.bufferRef = new AtomicReference<>(); this.exceptionRef = new AtomicReference<>(); } public ClassicToAsyncRequestProducer(final ClassicHttpRequest request, final Timeout timeout) { this(request, ClassicToAsyncSupport.INITIAL_BUF_SIZE, timeout); } void propagateException() throws IOException { final Exception ex = exceptionRef.getAndSet(null); if (ex != null) { ClassicToAsyncSupport.rethrow(ex); } } public IORunnable blockWaiting() throws IOException, InterruptedException { if (timeout == null) { countDownLatch.await(); } else { if (!countDownLatch.await(timeout.getDuration(), timeout.getTimeUnit())) { throw new InterruptedIOException("Timeout blocked waiting for output (" + timeout + ")"); } } propagateException(); final SharedOutputBuffer outputBuffer = bufferRef.get(); return () -> { final HttpEntity requestEntity = request.getEntity(); if (requestEntity != null) { try (final InternalOutputStream outputStream = new InternalOutputStream(outputBuffer)) { requestEntity.writeTo(outputStream); } } }; } @Override public void sendRequest(final RequestChannel channel, final HttpContext context) throws HttpException, IOException { final HttpEntity requestEntity = request.getEntity(); final SharedOutputBuffer buffer = requestEntity != null ? new SharedOutputBuffer(initialBufferSize) : null; bufferRef.set(buffer); repeatable = requestEntity == null || requestEntity.isRepeatable(); channel.sendRequest(request, requestEntity, null); countDownLatch.countDown(); } @Override public boolean isRepeatable() { return repeatable; } @Override public int available() { final SharedOutputBuffer buffer = bufferRef.get(); if (buffer != null) { return buffer.length(); } return 0; } @Override public void produce(final DataStreamChannel channel) throws IOException { final SharedOutputBuffer buffer = bufferRef.get(); if (buffer != null) { buffer.flush(channel); } } @Override public void failed(final Exception cause) { try { exceptionRef.set(cause); } finally { countDownLatch.countDown(); } } @Override public void releaseResources() { } class InternalOutputStream extends OutputStream { private final SharedOutputBuffer buffer; public InternalOutputStream(final SharedOutputBuffer buffer) { Asserts.notNull(buffer, "Shared buffer"); this.buffer = buffer; } @Override public void close() throws IOException { propagateException(); this.buffer.writeCompleted(timeout); } @Override public void flush() throws IOException { propagateException(); } @Override public void write(final byte[] b, final int off, final int len) throws IOException { propagateException(); this.buffer.write(b, off, len, timeout); } @Override public void write(final byte[] b) throws IOException { propagateException(); if (b == null) { return; } this.buffer.write(b, 0, b.length, timeout); } @Override public void write(final int b) throws IOException { propagateException(); this.buffer.write(b, timeout); } } } ././@LongLink0100644 0000000 0000000 00000000151 00000250600 011606 Lustar 0000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ClassicToAsyncResponseConsumer.javahttpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ClassicToAsyncResponseConsumer.0100644 0000000 0000000 00000024735 00000250600 032605 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support.classic; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.io.entity.AbstractHttpEntity; import org.apache.hc.core5.http.io.support.ClassicResponseBuilder; import org.apache.hc.core5.http.nio.AsyncResponseConsumer; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.io.Closer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; import org.apache.hc.core5.util.Timeout; /** * @since 5.4 */ @Experimental public class ClassicToAsyncResponseConsumer implements AsyncResponseConsumer { static class ResponseData { final HttpResponse head; final EntityDetails entityDetails; ResponseData(final HttpResponse head, final EntityDetails entityDetails) { this.head = head; this.entityDetails = entityDetails; } } private final int initialBufferSize; private final Timeout timeout; private final CountDownLatch countDownLatch; private final AtomicReference responseRef; private final AtomicReference> callbackRef; private final AtomicReference bufferRef; private final AtomicReference exceptionRef; public ClassicToAsyncResponseConsumer(final int initialBufferSize, final Timeout timeout) { this.initialBufferSize = Args.positive(initialBufferSize, "Initial buffer size"); this.timeout = timeout; this.countDownLatch = new CountDownLatch(1); this.responseRef = new AtomicReference<>(); this.callbackRef = new AtomicReference<>(); this.bufferRef = new AtomicReference<>(); this.exceptionRef = new AtomicReference<>(); } public ClassicToAsyncResponseConsumer(final Timeout timeout) { this(ClassicToAsyncSupport.INITIAL_BUF_SIZE, timeout); } void propagateException() throws IOException { final Exception ex = exceptionRef.getAndSet(null); if (ex != null) { ClassicToAsyncSupport.rethrow(ex); } } void fireComplete() throws IOException { final FutureCallback callback = callbackRef.getAndSet(null); if (callback != null) { callback.completed(null); } } public ClassicHttpResponse blockWaiting() throws IOException, InterruptedException { if (timeout == null) { countDownLatch.await(); } else { if (!countDownLatch.await(timeout.getDuration(), timeout.getTimeUnit())) { throw new InterruptedIOException("Timeout blocked waiting for input (" + timeout + ")"); } } propagateException(); final ResponseData r = responseRef.getAndSet(null); Asserts.notNull(r, "HTTP response is missing"); final SharedInputBuffer inputBuffer = bufferRef.get(); return ClassicResponseBuilder.copy(r.head) .setEntity(r.entityDetails != null ? new IncomingHttpEntity(new InternalInputStream(inputBuffer), r.entityDetails) : null) .build(); } @Override public void consumeResponse(final HttpResponse asyncResponse, final EntityDetails entityDetails, final HttpContext context, final FutureCallback resultCallback) throws HttpException, IOException { callbackRef.set(resultCallback); final ResponseData responseData = new ResponseData(asyncResponse, entityDetails); responseRef.set(responseData); if (entityDetails != null) { bufferRef.set(new SharedInputBuffer(initialBufferSize)); } else { fireComplete(); } countDownLatch.countDown(); } @Override public void informationResponse(final HttpResponse response, final HttpContext context) throws HttpException, IOException { } @Override public final void updateCapacity(final CapacityChannel capacityChannel) throws IOException { final SharedInputBuffer buffer = bufferRef.get(); if (buffer != null) { buffer.updateCapacity(capacityChannel); } } @Override public final void consume(final ByteBuffer src) throws IOException { final SharedInputBuffer buffer = bufferRef.get(); if (buffer != null) { buffer.fill(src); } } @Override public final void streamEnd(final List trailers) throws HttpException, IOException { final SharedInputBuffer buffer = bufferRef.get(); if (buffer != null) { buffer.markEndStream(); } } @Override public final void failed(final Exception cause) { try { exceptionRef.set(cause); } finally { countDownLatch.countDown(); } } @Override public void releaseResources() { if (countDownLatch.getCount() > 0) { countDownLatch.countDown(); } } class InternalInputStream extends InputStream { private final SharedInputBuffer buffer; InternalInputStream(final SharedInputBuffer buffer) { super(); Args.notNull(buffer, "Input buffer"); this.buffer = buffer; } @Override public int available() throws IOException { propagateException(); return this.buffer.length(); } @Override public int read(final byte[] b, final int off, final int len) throws IOException { propagateException(); if (len == 0) { return 0; } final int bytesRead = this.buffer.read(b, off, len, timeout); if (bytesRead == -1) { fireComplete(); } return bytesRead; } @Override public int read(final byte[] b) throws IOException { propagateException(); if (b == null) { return 0; } final int bytesRead = this.buffer.read(b, 0, b.length, timeout); if (bytesRead == -1) { fireComplete(); } return bytesRead; } @Override public int read() throws IOException { propagateException(); final int b = this.buffer.read(timeout); if (b == -1) { fireComplete(); } return b; } @Override public void close() throws IOException { // read and discard the remainder of the message final byte[] tmp = new byte[1024]; do { /* empty */ } while (read(tmp) >= 0); super.close(); } } static class IncomingHttpEntity implements HttpEntity { private final InputStream content; private final EntityDetails entityDetails; IncomingHttpEntity(final InputStream content, final EntityDetails entityDetails) { this.content = content; this.entityDetails = entityDetails; } @Override public boolean isRepeatable() { return false; } @Override public boolean isChunked() { return entityDetails.isChunked(); } @Override public long getContentLength() { return entityDetails.getContentLength(); } @Override public String getContentType() { return entityDetails.getContentType(); } @Override public String getContentEncoding() { return entityDetails.getContentEncoding(); } @Override public InputStream getContent() throws IOException, IllegalStateException { return content; } @Override public boolean isStreaming() { return content != null; } @Override public void writeTo(final OutputStream outStream) throws IOException { AbstractHttpEntity.writeTo(this, outStream); } @Override public Supplier> getTrailers() { return null; } @Override public Set getTrailerNames() { return Collections.emptySet(); } @Override public void close() throws IOException { Closer.close(content); } @Override public String toString() { return entityDetails.toString(); } } } ././@LongLink0100644 0000000 0000000 00000000156 00000250600 011613 Lustar 0000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ClassicToAsyncServerExchangeHandler.javahttpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ClassicToAsyncServerExchangeHan0100644 0000000 0000000 00000034063 00000250600 032550 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support.classic; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestMapper; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.impl.io.support.IncomingHttpEntity; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.HttpServerRequestHandler; import org.apache.hc.core5.http.io.support.BasicHttpServerRequestHandler; import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; import org.apache.hc.core5.http.nio.AsyncResponseProducer; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.nio.support.BasicResponseProducer; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.support.BasicResponseBuilder; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; /** * {@link AsyncServerExchangeHandler} implementation that acts as a compatibility * layer for classic {@link InputStream} / {@link OutputStream} based interfaces. * Blocking input / output processing is executed through an {@link Executor}. * * @since 5.4 */ @Experimental public class ClassicToAsyncServerExchangeHandler implements AsyncServerExchangeHandler { private final int initialBufferSize; private final Executor executor; private final HttpServerRequestHandler requestHandler; private final Callback exceptionCallback; private final AtomicBoolean responseCommitted; private final AtomicReference responseProducerRef; private final AtomicReference inputBufferRef; private final AtomicReference outputBufferRef; private final AtomicReference exceptionRef; public ClassicToAsyncServerExchangeHandler( final int initialBufferSize, final Executor executor, final HttpServerRequestHandler requestHandler, final Callback exceptionCallback) { this.initialBufferSize = Args.positive(initialBufferSize, "Initial buffer size"); this.executor = Args.notNull(executor, "Executor"); this.requestHandler = Args.notNull(requestHandler, "Request handler"); this.exceptionCallback = exceptionCallback; this.responseCommitted = new AtomicBoolean(); this.responseProducerRef = new AtomicReference<>(); this.inputBufferRef = new AtomicReference<>(); this.outputBufferRef = new AtomicReference<>(); this.exceptionRef = new AtomicReference<>(); } public ClassicToAsyncServerExchangeHandler( final Executor executor, final HttpServerRequestHandler requestHandler, final Callback exceptionCallback) { this(ClassicToAsyncSupport.INITIAL_BUF_SIZE, executor, requestHandler, exceptionCallback); } public ClassicToAsyncServerExchangeHandler( final Executor executor, final HttpRequestMapper handlerMapper, final Callback exceptionCallback) { this(ClassicToAsyncSupport.INITIAL_BUF_SIZE, executor, new BasicHttpServerRequestHandler(handlerMapper), exceptionCallback); } public ClassicToAsyncServerExchangeHandler( final Executor executor, final HttpRequestHandler handler, final Callback exceptionCallback) { this(ClassicToAsyncSupport.INITIAL_BUF_SIZE, executor, new BasicHttpServerRequestHandler((request, context) -> handler), exceptionCallback); } void propagateException() throws IOException { final Exception ex = exceptionRef.getAndSet(null); if (ex != null) { ClassicToAsyncSupport.rethrow(ex); } } SharedInputBuffer inputBuffer() { final SharedInputBuffer inputBuffer = inputBufferRef.get(); Asserts.notNull(inputBuffer, "Input buffer"); return inputBuffer; } SharedOutputBuffer outputBuffer() { final SharedOutputBuffer outputBuffer = outputBufferRef.get(); Asserts.notNull(outputBuffer, "Output buffer"); return outputBuffer; } void abortInput() { final SharedInputBuffer inputBuffer = inputBufferRef.get(); if (inputBuffer != null) { inputBuffer.abort(); } } void abortOutput() { final SharedOutputBuffer outputBuffer = outputBufferRef.get(); if (outputBuffer != null) { outputBuffer.abort(); } } @Override public final void handleRequest( final HttpRequest request, final EntityDetails entityDetails, final ResponseChannel responseChannel, final HttpContext context) throws HttpException, IOException { if (entityDetails != null) { final SharedInputBuffer inputBuffer = new SharedInputBuffer(initialBufferSize); inputBufferRef.set(inputBuffer); } executor.execute(() -> { try { final ClassicHttpRequest cr = ClassicRequestBuilder.copy(request).build(); if (entityDetails != null) { cr.setEntity(new IncomingHttpEntity( new InternalInputStream(inputBufferRef.get()), entityDetails.getContentLength(), request)); } final HttpServerRequestHandler.ResponseTrigger trigger = new HttpServerRequestHandler.ResponseTrigger() { @Override public void sendInformation(final ClassicHttpResponse response) throws HttpException, IOException { responseChannel.sendInformation(response, context); } @Override public void submitResponse(final ClassicHttpResponse response) throws HttpException, IOException { if (responseCommitted.compareAndSet(false, true)) { final HttpEntity responseEntity = response.getEntity(); final String method = request.getMethod(); final boolean contentExpected = responseEntity != null && !Method.HEAD.isSame(method); if (contentExpected) { final SharedOutputBuffer outputBuffer = new SharedOutputBuffer(initialBufferSize); outputBufferRef.set(outputBuffer); } responseChannel.sendResponse(response, responseEntity, null); if (contentExpected) { responseEntity.writeTo(new InternalOutputStream(outputBufferRef.get())); } } else { throw new IllegalStateException("Response has already been committed"); } } }; try { requestHandler.handle(cr, trigger, context); } catch (HttpException | RuntimeException ex) { if (responseCommitted.compareAndSet(false, true)) { final AsyncResponseProducer responseProducer = handleError(ex); responseProducerRef.set(responseProducer); responseProducer.sendResponse(responseChannel, context); } else { throw ex; } } } catch (final Exception ex) { if (exceptionCallback != null) { exceptionCallback.execute(ex); } responseChannel.terminateExchange(); } }); } protected AsyncResponseProducer handleError(final Exception ex) { final int status = (ex instanceof ProtocolException) ? HttpStatus.SC_BAD_REQUEST : HttpStatus.SC_INTERNAL_SERVER_ERROR; return new BasicResponseProducer( BasicResponseBuilder.create(status).build(), ex.getMessage(), ContentType.TEXT_PLAIN); } @Override public final void updateCapacity(final CapacityChannel capacityChannel) throws IOException { inputBuffer().updateCapacity(capacityChannel); } @Override public final void consume(final ByteBuffer src) throws IOException { inputBuffer().fill(src); } @Override public final void streamEnd(final List trailers) throws HttpException, IOException { inputBuffer().markEndStream(); } @Override public final int available() { final AsyncResponseProducer responseProducer = responseProducerRef.get(); if (responseProducer != null) { return responseProducer.available(); } else { return outputBuffer().length(); } } @Override public final void produce(final DataStreamChannel channel) throws IOException { final AsyncResponseProducer responseProducer = responseProducerRef.get(); if (responseProducer != null) { responseProducer.produce(channel); } else { outputBuffer().flush(channel); } } @Override public final void failed(final Exception cause) { responseCommitted.set(true); exceptionRef.compareAndSet(null, cause); abortInput(); abortOutput(); } @Override public void releaseResources() { } class InternalInputStream extends InputStream { private final ContentInputBuffer buffer; InternalInputStream(final ContentInputBuffer buffer) { super(); Args.notNull(buffer, "Input buffer"); this.buffer = buffer; } @Override public int available() throws IOException { propagateException(); return buffer.length(); } @Override public int read(final byte[] b, final int off, final int len) throws IOException { propagateException(); if (len == 0) { return 0; } return buffer.read(b, off, len); } @Override public int read(final byte[] b) throws IOException { propagateException(); if (b == null) { return 0; } return buffer.read(b, 0, b.length); } @Override public int read() throws IOException { propagateException(); return buffer.read(); } @Override public void close() throws IOException { propagateException(); // read and discard the remainder of the message final byte[] tmp = new byte[1024]; do { /* empty */ } while (read(tmp) >= 0); super.close(); } } class InternalOutputStream extends OutputStream { private final SharedOutputBuffer buffer; public InternalOutputStream(final SharedOutputBuffer buffer) { Asserts.notNull(buffer, "Shared buffer"); this.buffer = buffer; } @Override public void close() throws IOException { propagateException(); buffer.writeCompleted(); } @Override public void flush() throws IOException { propagateException(); } @Override public void write(final byte[] b, final int off, final int len) throws IOException { propagateException(); buffer.write(b, off, len); } @Override public void write(final byte[] b) throws IOException { propagateException(); if (b == null) { return; } buffer.write(b, 0, b.length); } @Override public void write(final int b) throws IOException { propagateException(); buffer.write(b); } } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ClassicToAsyncSupport.java0100644 0000000 0000000 00000004135 00000250600 031601 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support.classic; import java.io.IOException; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.HttpException; final class ClassicToAsyncSupport { final static int INITIAL_BUF_SIZE = 2048; static void rethrow(final Throwable ex) throws IOException { if (ex instanceof Error) { throw (Error) ex; } else if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } else if (ex instanceof ConnectionClosedException) { throw (ConnectionClosedException) ex; } else if (ex instanceof IOException) { throw new TransportException((IOException) ex); } else if (ex instanceof HttpException) { throw new ProtocolException((HttpException) ex); } else { // Unexpected exception type throw new IllegalStateException(ex); } } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ContentInputBuffer.java0100644 0000000 0000000 00000006115 00000250600 031106 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support.classic; import java.io.IOException; /** * Generic content input buffer. * * @since 4.0 */ public interface ContentInputBuffer { /** * Return length data stored in the buffer * * @return data length */ int length(); /** * Resets the buffer by clearing its state and stored content. */ void reset(); /** * Reads up to {@code len} bytes of data from this buffer into * an array of bytes. The exact number of bytes read depends how many bytes * are stored in the buffer. * *

If {@code off} is negative, or {@code len} is negative, or * {@code off+len} is greater than the length of the array * {@code b}, this method can throw a runtime exception. The exact type * of runtime exception thrown by this method depends on implementation. * This method returns {@code -1} if the end of content stream has been * reached. * * @param b the buffer into which the data is read. * @param off the start offset in array {@code b} * at which the data is written. * @param len the maximum number of bytes to 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 IOException if an I/O error occurs. */ int read(byte[] b, int off, int len) throws IOException; /** * Reads one byte from this buffer. If the buffer is empty this method can * throw a runtime exception. The exact type of runtime exception thrown * by this method depends on implementation. This method returns * {@code -1} if the end of content stream has been reached. * * @return one byte * @throws IOException in case of an I/O error. */ int read() throws IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ContentInputStream.java0100644 0000000 0000000 00000005015 00000250600 031126 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support.classic; import java.io.IOException; import java.io.InputStream; import org.apache.hc.core5.util.Args; /** * {@link InputStream} adaptor for {@link ContentInputBuffer}. * * @since 4.0 */ public class ContentInputStream extends InputStream { private final ContentInputBuffer buffer; public ContentInputStream(final ContentInputBuffer buffer) { super(); Args.notNull(buffer, "Input buffer"); this.buffer = buffer; } @Override public int available() throws IOException { return this.buffer.length(); } @Override public int read(final byte[] b, final int off, final int len) throws IOException { if (len == 0) { return 0; } return this.buffer.read(b, off, len); } @Override public int read(final byte[] b) throws IOException { if (b == null) { return 0; } return this.buffer.read(b, 0, b.length); } @Override public int read() throws IOException { return this.buffer.read(); } @Override public void close() throws IOException { // read and discard the remainder of the message final byte[] tmp = new byte[1024]; while (this.buffer.read(tmp, 0, tmp.length) >= 0) { // ignored } super.close(); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ContentOutputBuffer.java0100644 0000000 0000000 00000005144 00000250600 031310 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support.classic; import java.io.IOException; /** * Generic content output buffer. * * @since 4.0 */ public interface ContentOutputBuffer { /** * Return length data stored in the buffer * * @return data length */ int length(); /** * Resets the buffer by clearing its state and stored content. */ void reset(); /** * Writes {@code len} bytes from the specified byte array * starting at offset {@code off} to this buffer. *

* If {@code off} is negative, or {@code len} is negative, or * {@code off+len} is greater than the length of the array * {@code b}, this method can throw a runtime exception. The exact type * of runtime exception thrown by this method depends on implementation. * * @param b the data. * @param off the start offset in the data. * @param len the number of bytes to write. * @throws IOException if an I/O error occurs. */ void write(byte[] b, int off, int len) throws IOException; /** * Writes the specified byte to this buffer. * * @param b the {@code byte}. * @throws IOException if an I/O error occurs. */ void write(int b) throws IOException; /** * Indicates the content has been fully written. * @throws IOException if an I/O error occurs. */ void writeCompleted() throws IOException; } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ContentOutputStream.java0100644 0000000 0000000 00000004402 00000250600 031326 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support.classic; import java.io.IOException; import java.io.OutputStream; import org.apache.hc.core5.util.Args; /** * {@link OutputStream} adaptor for {@link ContentOutputBuffer}. * * @since 4.0 */ public class ContentOutputStream extends OutputStream { private final ContentOutputBuffer buffer; public ContentOutputStream(final ContentOutputBuffer buffer) { super(); Args.notNull(buffer, "Output buffer"); this.buffer = buffer; } @Override public void close() throws IOException { this.buffer.writeCompleted(); } @Override public void flush() throws IOException { } @Override public void write(final byte[] b, final int off, final int len) throws IOException { this.buffer.write(b, off, len); } @Override public void write(final byte[] b) throws IOException { if (b == null) { return; } this.buffer.write(b, 0, b.length); } @Override public void write(final int b) throws IOException { this.buffer.write(b); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ProtocolException.java0100644 0000000 0000000 00000002776 00000250600 031013 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support.classic; import java.io.IOException; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.HttpException; /** * @since 5.4 */ @Internal public class ProtocolException extends IOException { public ProtocolException(final HttpException ex) { super(ex); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/SharedInputBuffer.java0100644 0000000 0000000 00000014347 00000250600 030710 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support.classic; import java.io.IOException; import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.util.Timeout; /** * @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE) public final class SharedInputBuffer extends AbstractSharedBuffer implements ContentInputBuffer { private final int initialBufferSize; private final AtomicInteger capacityIncrement; private volatile CapacityChannel capacityChannel; public SharedInputBuffer(final ReentrantLock lock, final int initialBufferSize) { super(lock, initialBufferSize); this.initialBufferSize = initialBufferSize; this.capacityIncrement = new AtomicInteger(0); } public SharedInputBuffer(final int bufferSize) { this(new ReentrantLock(), bufferSize); } public int fill(final ByteBuffer src) { lock.lock(); try { setInputMode(); ensureAdjustedCapacity(buffer().position() + src.remaining()); buffer().put(src); final int remaining = buffer().remaining(); condition.signalAll(); return remaining; } finally { lock.unlock(); } } private void incrementCapacity() throws IOException { if (capacityChannel != null) { final int increment = capacityIncrement.getAndSet(0); if (increment > 0) { capacityChannel.update(increment); } } } public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { lock.lock(); try { this.capacityChannel = capacityChannel; setInputMode(); if (buffer().position() == 0) { capacityChannel.update(initialBufferSize); } } finally { lock.unlock(); } } private void awaitInput(final Timeout timeout) throws InterruptedIOException { if (!buffer().hasRemaining()) { setInputMode(); while (buffer().position() == 0 && !endStream && !aborted) { try { if (timeout == null) { condition.await(); } else { if (!condition.await(timeout.getDuration(), timeout.getTimeUnit())) { throw new InterruptedIOException("Timeout blocked waiting for input (" + timeout + ")"); } } } catch (final InterruptedException ex) { Thread.currentThread().interrupt(); throw new InterruptedIOException(ex.getMessage()); } } setOutputMode(); } } private void ensureNotAborted() throws InterruptedIOException { if (aborted) { throw new InterruptedIOException("Operation aborted"); } } @Override public int read() throws IOException { return read(null); } /** * @since 5.4 */ public int read(final Timeout timeout) throws IOException { lock.lock(); try { setOutputMode(); awaitInput(timeout); ensureNotAborted(); if (!buffer().hasRemaining() && endStream) { return -1; } final int b = buffer().get() & 0xff; capacityIncrement.incrementAndGet(); if (!buffer().hasRemaining()) { incrementCapacity(); } return b; } finally { lock.unlock(); } } @Override public int read(final byte[] b, final int off, final int len) throws IOException { return read(b, off, len, null); } /** * @since 5.4 */ public int read(final byte[] b, final int off, final int len, final Timeout timeout) throws IOException { if (len == 0) { return 0; } lock.lock(); try { setOutputMode(); awaitInput(timeout); ensureNotAborted(); if (!buffer().hasRemaining() && endStream) { return -1; } final int chunk = Math.min(buffer().remaining(), len); buffer().get(b, off, chunk); capacityIncrement.addAndGet(chunk); if (!buffer().hasRemaining()) { incrementCapacity(); } return chunk; } finally { lock.unlock(); } } public void markEndStream() { if (endStream) { return; } lock.lock(); try { if (!endStream) { endStream = true; capacityChannel = null; condition.signalAll(); } } finally { lock.unlock(); } } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/SharedOutputBuffer.java0100644 0000000 0000000 00000015631 00000250600 031106 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support.classic; import java.io.IOException; import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.util.Timeout; /** * @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE) public final class SharedOutputBuffer extends AbstractSharedBuffer implements ContentOutputBuffer { private final AtomicBoolean endStreamPropagated; private volatile DataStreamChannel dataStreamChannel; private volatile boolean hasCapacity; public SharedOutputBuffer(final ReentrantLock lock, final int initialBufferSize) { super(lock, initialBufferSize); this.hasCapacity = false; this.endStreamPropagated = new AtomicBoolean(); } public SharedOutputBuffer(final int bufferSize) { this(new ReentrantLock(), bufferSize); } public void flush(final DataStreamChannel channel) throws IOException { lock.lock(); try { dataStreamChannel = channel; hasCapacity = true; setOutputMode(); if (buffer().hasRemaining()) { dataStreamChannel.write(buffer()); } if (!buffer().hasRemaining() && endStream) { propagateEndStream(); } condition.signalAll(); } finally { lock.unlock(); } } private void ensureNotAborted() throws InterruptedIOException { if (aborted) { throw new InterruptedIOException("Operation aborted"); } } /** * @since 5.4 */ public void write(final byte[] b, final int off, final int len, final Timeout timeout) throws IOException { final ByteBuffer src = ByteBuffer.wrap(b, off, len); lock.lock(); try { ensureNotAborted(); setInputMode(); while (src.hasRemaining()) { // always buffer small chunks if (src.remaining() < 1024 && buffer().remaining() > src.remaining()) { buffer().put(src); } else { if (buffer().position() > 0 || dataStreamChannel == null) { waitFlush(timeout); } if (buffer().position() == 0 && dataStreamChannel != null) { final int bytesWritten = dataStreamChannel.write(src); if (bytesWritten == 0) { hasCapacity = false; waitFlush(timeout); } } } } } finally { lock.unlock(); } } @Override public void write(final byte[] b, final int off, final int len) throws IOException { write(b, off, len, null); } /** * @since 5.4 */ public void write(final int b, final Timeout timeout) throws IOException { lock.lock(); try { ensureNotAborted(); setInputMode(); if (!buffer().hasRemaining()) { waitFlush(timeout); } buffer().put((byte)b); } finally { lock.unlock(); } } @Override public void write(final int b) throws IOException { write(b, null); } /** * @since 5.4 */ public void writeCompleted(final Timeout timeout) throws IOException { if (endStream) { return; } lock.lock(); try { if (!endStream) { endStream = true; if (dataStreamChannel != null) { setOutputMode(); if (buffer().hasRemaining()) { dataStreamChannel.requestOutput(); waitEndStream(timeout); } else { propagateEndStream(); } } } } finally { lock.unlock(); } } @Override public void writeCompleted() throws IOException { writeCompleted(null); } private void waitFlush(final Timeout timeout) throws InterruptedIOException { if (dataStreamChannel != null) { dataStreamChannel.requestOutput(); } setOutputMode(); while (buffer().hasRemaining() || !hasCapacity) { ensureNotAborted(); waitForSignal(timeout); } setInputMode(); } private void waitEndStream(final Timeout timeout) throws InterruptedIOException { if (dataStreamChannel != null) { dataStreamChannel.requestOutput(); } while (!endStreamPropagated.get() && !aborted) { waitForSignal(timeout); } } private void waitForSignal(final Timeout timeout) throws InterruptedIOException { try { if (timeout == null) { condition.await(); } else { if (!condition.await(timeout.getDuration(), timeout.getTimeUnit())) { aborted = true; throw new InterruptedIOException("Timeout blocked waiting for output (" + timeout + ")"); } } } catch (final InterruptedException ex) { Thread.currentThread().interrupt(); throw new InterruptedIOException(ex.getMessage()); } } private void propagateEndStream() throws IOException { if (endStreamPropagated.compareAndSet(false, true)) { dataStreamChannel.endStream(); } } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/TransportException.java0100644 0000000 0000000 00000002717 00000250600 031201 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support.classic; import java.io.IOException; import org.apache.hc.core5.annotation.Internal; /** * @since 5.4 */ @Internal public class TransportException extends IOException { public TransportException(final IOException ex) { super(ex); } } httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/package-info.java0100644 0000000 0000000 00000002517 00000250600 027650 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Support classes for the asynchronous I/O model that emulate * behavior of the classic (blocking) I/O model. */ package org.apache.hc.core5.http.nio.support.classic; httpcore5/src/main/java/org/apache/hc/core5/http/protocol/0040755 0000000 0000000 00000000000 00000250600 022376 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/protocol/BasicHttpContext.java0100644 0000000 0000000 00000006515 00000250600 026473 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.util.Args; /** * Default implementation of {@link HttpContext}. *

* Please note instances of this class can be thread unsafe if the * parent context is not thread safe. * * @deprecated Do not use. */ @Deprecated @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL) public class BasicHttpContext implements HttpContext { private final HttpContext parentContext; private final Map map; private ProtocolVersion version; public BasicHttpContext() { this(null); } public BasicHttpContext(final HttpContext parentContext) { super(); this.map = new ConcurrentHashMap<>(); this.parentContext = parentContext; } @Override public Object getAttribute(final String id) { Args.notNull(id, "Id"); Object obj = this.map.get(id); if (obj == null && this.parentContext != null) { obj = this.parentContext.getAttribute(id); } return obj; } @Override public Object setAttribute(final String id, final Object obj) { Args.notNull(id, "Id"); if (obj != null) { return this.map.put(id, obj); } return this.map.remove(id); } @Override public Object removeAttribute(final String id) { Args.notNull(id, "Id"); return this.map.remove(id); } /** * @since 5.0 */ @Override public ProtocolVersion getProtocolVersion() { return this.version != null ? this.version : HttpVersion.DEFAULT; } /** * @since 5.0 */ @Override public void setProtocolVersion(final ProtocolVersion version) { this.version = version; } /** * @since 4.2 */ public void clear() { this.map.clear(); } @Override public String toString() { return this.map.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ChainBuilder.java0100644 0000000 0000000 00000006540 00000250600 025574 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; /** * Builder class to build a linked list (chain) of unique class instances. Each class can have * only one instance in the list. Useful for building lists of protocol interceptors. * * @see DefaultHttpProcessor * * @since 4.3 */ final class ChainBuilder { private final LinkedList list; private final Map, E> uniqueClasses; public ChainBuilder() { this.list = new LinkedList<>(); this.uniqueClasses = new HashMap<>(); } private void ensureUnique(final E e) { final E previous = this.uniqueClasses.remove(e.getClass()); if (previous != null) { this.list.remove(previous); } this.uniqueClasses.put(e.getClass(), e); } public ChainBuilder addFirst(final E e) { if (e == null) { return this; } ensureUnique(e); this.list.addFirst(e); return this; } public ChainBuilder addLast(final E e) { if (e == null) { return this; } ensureUnique(e); this.list.addLast(e); return this; } public ChainBuilder addAllFirst(final Collection c) { if (c == null) { return this; } for (final E e: c) { addFirst(e); } return this; } @SafeVarargs public final ChainBuilder addAllFirst(final E... c) { if (c == null) { return this; } for (final E e: c) { addFirst(e); } return this; } public ChainBuilder addAllLast(final Collection c) { if (c == null) { return this; } for (final E e: c) { addLast(e); } return this; } @SafeVarargs public final ChainBuilder addAllLast(final E... c) { if (c == null) { return this; } for (final E e: c) { addLast(e); } return this; } public LinkedList build() { return new LinkedList<>(this.list); } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/DefaultHttpProcessor.java0100644 0000000 0000000 00000011242 00000250600 027362 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.io.IOException; import java.util.List; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponseInterceptor; /** * Default immutable implementation of {@link HttpProcessor}. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) public final class DefaultHttpProcessor implements HttpProcessor { private final HttpRequestInterceptor[] requestInterceptors; private final HttpResponseInterceptor[] responseInterceptors; public DefaultHttpProcessor( final HttpRequestInterceptor[] requestInterceptors, final HttpResponseInterceptor[] responseInterceptors) { super(); if (requestInterceptors != null) { final int l = requestInterceptors.length; this.requestInterceptors = new HttpRequestInterceptor[l]; System.arraycopy(requestInterceptors, 0, this.requestInterceptors, 0, l); } else { this.requestInterceptors = new HttpRequestInterceptor[0]; } if (responseInterceptors != null) { final int l = responseInterceptors.length; this.responseInterceptors = new HttpResponseInterceptor[l]; System.arraycopy(responseInterceptors, 0, this.responseInterceptors, 0, l); } else { this.responseInterceptors = new HttpResponseInterceptor[0]; } } /** * @since 4.3 */ public DefaultHttpProcessor( final List requestInterceptors, final List responseInterceptors) { super(); if (requestInterceptors != null) { final int l = requestInterceptors.size(); this.requestInterceptors = requestInterceptors.toArray(new HttpRequestInterceptor[l]); } else { this.requestInterceptors = new HttpRequestInterceptor[0]; } if (responseInterceptors != null) { final int l = responseInterceptors.size(); this.responseInterceptors = responseInterceptors.toArray(new HttpResponseInterceptor[l]); } else { this.responseInterceptors = new HttpResponseInterceptor[0]; } } public DefaultHttpProcessor(final HttpRequestInterceptor... requestInterceptors) { this(requestInterceptors, null); } public DefaultHttpProcessor(final HttpResponseInterceptor... responseInterceptors) { this(null, responseInterceptors); } @Override public void process( final HttpRequest request, final EntityDetails entity, final HttpContext context) throws IOException, HttpException { for (final HttpRequestInterceptor requestInterceptor : this.requestInterceptors) { requestInterceptor.process(request, entity, context); } } @Override public void process( final HttpResponse response, final EntityDetails entity, final HttpContext context) throws IOException, HttpException { for (final HttpResponseInterceptor responseInterceptor : this.responseInterceptors) { responseInterceptor.process(response, entity, context); } } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ForwardedRequest.java0100644 0000000 0000000 00000021431 00000250600 026525 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.io.IOException; import java.net.Inet6Address; import java.net.InetSocketAddress; import java.net.SocketAddress; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EndpointDetails; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.util.Args; /** * This request interceptor can be used by an HTTP proxy or an intermediary to add a Forwarded header * to outgoing request messages. * The Forwarded header is used to capture information about the intermediate nodes that a request * has passed through. This information can be useful for security purposes or for debugging purposes. *

* The Forwarded header consists of a list of key-value pairs separated by semicolons. The keys that * can be used in the Forwarded header include "host", "port", "proto", "for", and "by". The host * key is used to specify the host name or IP address of the request authority. The port key is used * to specify the port number of the request authority. The proto key is used to specify the * protocol version of the request. The for key is used to specify the IP address of the client * making the request. The by key is used to specify the IP address of the node adding the Forwarded * header. *

* When multiple proxy servers are involved in forwarding a request, each proxy can add its own * Forwarded header to the request. This allows for the capture of information about each * intermediate node that the request passes through. *

* The ForwardedRequest class adds the Forwarded header to the request by implementing the process() * method of the HttpRequestInterceptor interface. The method retrieves the ProtocolVersion and * {@link URIAuthority} from the {@link HttpContext}. The ProtocolVersion is used to determine the * proto key value and the {@link URIAuthority} is used to determine the host and port key values. * The method also retrieves the {@link EndpointDetails} from the {@link HttpContext}, if it exists. * The {@link EndpointDetails} is used to determine the by and for key values. If a Forwarded header * already exists in the request, the existing header is not overwritten; instead, the new header * value is appended to the existing header value, with a comma separator. *

* * @since 5.3 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class ForwardedRequest implements HttpRequestInterceptor { /** * The name of the header to set in the HTTP request. */ private static final String FORWARDED_HEADER_NAME = "Forwarded"; /** * Singleton instance. */ public static final HttpRequestInterceptor INSTANCE = new ForwardedRequest(); /** * The process method adds a Forwarded header to an HTTP request containing information about * the intermediate nodes that the request has passed through. If a Forwarded header already * exists in the request, the new header value is appended to the existing header value, with a * comma separator. *

* The method retrieves the {@link ProtocolVersion} and {@link URIAuthority} from the * HttpContext. The ProtocolVersion is used to determine the proto key value and the * URIAuthority is used to determine the host and port key values. The method also retrieves the * EndpointDetails from the {@link HttpContext}, if it exists. The {@link EndpointDetails} is * used to determine the by key value. * * @param request the HTTP request to which the Forwarded header is to be added (not * {@code null}) * @param entity the entity details of the request (can be {@code null}) * @param localContext the HTTP context in which the request is being executed (not {@code null}) * @throws HttpException if there is an error processing the request * @throws IOException if an IO error occurs while processing the request * @throws ProtocolException if the request authority is not specified */ @Override public void process(final HttpRequest request, final EntityDetails entity, final HttpContext localContext) throws HttpException, IOException { Args.notNull(request, "HTTP request"); Args.notNull(localContext, "HTTP context"); final HttpCoreContext context = HttpCoreContext.cast(localContext); final ProtocolVersion ver = context.getProtocolVersion() != null ? context.getProtocolVersion() : HttpVersion.HTTP_1_1; final URIAuthority authority = request.getAuthority(); if (authority == null) { throw new ProtocolException("Request authority not specified"); } final int port = authority.getPort(); final StringBuilder valueBuilder = new StringBuilder(); final Header forwardedHeader = request.getFirstHeader(FORWARDED_HEADER_NAME); final boolean forwardedHeaderExists = forwardedHeader != null; if (forwardedHeaderExists) { // Forwarded header already exists, add current hop details to the end of the existing value valueBuilder.append(forwardedHeader.getValue()); valueBuilder.append(", "); } // Add the current hop details final EndpointDetails endpointDetails = context.getEndpointDetails(); // Add the "by" parameter if (endpointDetails != null) { final SocketAddress remoteAddress = endpointDetails.getRemoteAddress(); if (remoteAddress instanceof InetSocketAddress) { final InetSocketAddress inetAddress = (InetSocketAddress) remoteAddress; final String byValue = inetAddress.getHostString() + ":" + inetAddress.getPort(); if (inetAddress.getAddress() instanceof Inet6Address) { valueBuilder.append("by=\"").append(byValue).append("\""); } else { valueBuilder.append("by=").append(byValue); } } // Add the "for" parameter final SocketAddress localAddress = endpointDetails.getLocalAddress(); if (localAddress instanceof InetSocketAddress) { final InetSocketAddress inetAddress = (InetSocketAddress) localAddress; final String forValue = inetAddress.getHostString() + ":" + inetAddress.getPort(); if (inetAddress.getAddress() instanceof Inet6Address) { valueBuilder.append(";for=\"").append(forValue).append("\""); } else { valueBuilder.append(";for=").append(forValue); } } } // Add the "host" parameter if (valueBuilder.length() > 0 && !forwardedHeaderExists) { valueBuilder.append(";"); } valueBuilder.append("host=\"").append(authority.getHostName()).append("\""); // Add the "port" parameter if (port != -1) { valueBuilder.append(";port=").append(port); } // Add the "proto" parameter final String protoValue = ver.getProtocol(); if (protoValue != null) { valueBuilder.append(";proto=").append(protoValue); } final String value = valueBuilder.toString(); request.setHeader(FORWARDED_HEADER_NAME, value); } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/HttpContext.java0100644 0000000 0000000 00000006235 00000250600 025530 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import org.apache.hc.core5.http.ProtocolVersion; /** * HttpContext represents execution state of an HTTP process. It is a structure * that can be used to map an attribute name to an attribute value. *

* The primary purpose of the HTTP context is to facilitate information sharing * among various logically related components. HTTP context can be used * to store a processing state for one message or several consecutive messages. * Multiple logically related messages can participate in a logical session * if the same context is reused between consecutive messages. *

*

* IMPORTANT: Please note HTTP context implementation, even when thread safe, * may not be used concurrently by multiple threads, as the context may contain * thread unsafe attributes. *

* * @since 4.0 */ public interface HttpContext { /** The prefix reserved for use by HTTP components. "http." */ String RESERVED_PREFIX = "http."; /** * Returns protocol version used in this context. * * @since 5.0 */ ProtocolVersion getProtocolVersion(); /** * Sets protocol version used in this context. * * @since 5.0 */ void setProtocolVersion(ProtocolVersion version); /** * Obtains attribute with the given name. * * @param id the attribute name. * @return attribute value, or {@code null} if not set. */ Object getAttribute(String id); /** * Sets value of the attribute with the given name. * * @param id the attribute name. * @param obj the attribute value. * @return the previous value associated with {@code id}, or * {@code null} if there was no mapping for {@code id}. */ Object setAttribute(String id, Object obj); /** * Removes attribute with the given name from the context. * * @param id the attribute name. * @return attribute value, or {@code null} if not set. */ Object removeAttribute(String id); } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/HttpCoreContext.java0100644 0000000 0000000 00000024405 00000250600 026340 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.util.HashMap; import java.util.Map; import javax.net.ssl.SSLSession; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.EndpointDetails; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.util.Args; /** * Core execution {@link HttpContext}. *

* IMPORTANT: This class is NOT thread-safe and MUST NOT be used concurrently by * multiple message exchanges. * * @since 4.3 */ public class HttpCoreContext implements HttpContext { /** * @deprecated Use getter methods */ @Deprecated public static final String CONNECTION_ENDPOINT = HttpContext.RESERVED_PREFIX + "connection-endpoint"; /** * @deprecated Use getter methods */ @Deprecated public static final String SSL_SESSION = HttpContext.RESERVED_PREFIX + "ssl-session"; /** * @deprecated Use getter methods */ @Deprecated public static final String HTTP_REQUEST = HttpContext.RESERVED_PREFIX + "request"; /** * @deprecated Use getter methods */ @Deprecated public static final String HTTP_RESPONSE = HttpContext.RESERVED_PREFIX + "response"; public static HttpCoreContext create() { return new HttpCoreContext(); } /** * @deprecated Use {@link #cast(HttpContext)}. */ @Deprecated public static HttpCoreContext adapt(final HttpContext context) { if (context == null) { return new HttpCoreContext(); } if (context instanceof HttpCoreContext) { return (HttpCoreContext) context; } return new HttpCoreContext(context); } /** * Casts the given generic {@link HttpContext} as {@link HttpCoreContext}. * * @since 5.3 */ public static HttpCoreContext cast(final HttpContext context) { if (context == null) { return null; } return context instanceof HttpCoreContext ? (HttpCoreContext) context : new Delegate(context); } /** * Casts the given generic {@link HttpContext} as {@link HttpCoreContext} or * creates new {@link HttpCoreContext} if the given context is null. * * @since 5.4 */ public static HttpCoreContext castOrCreate(final HttpContext context) { return context != null ? cast(context) : create(); } private final HttpContext parentContext; private Map map; private ProtocolVersion version; private HttpRequest request; private HttpResponse response; private EndpointDetails endpointDetails; private SSLSession sslSession; public HttpCoreContext(final HttpContext parentContext) { super(); this.parentContext = parentContext; } public HttpCoreContext() { super(); this.parentContext = null; } /** * Represents the protocol version used by the message exchange. *

* This context attribute is expected to be populated by the protocol handler * in the course of request execution. * * @since 5.0 */ @Override public ProtocolVersion getProtocolVersion() { return this.version != null ? this.version : HttpVersion.HTTP_1_1; } /** * @since 5.0 */ @Override public void setProtocolVersion(final ProtocolVersion version) { this.version = version; } @Override public Object getAttribute(final String id) { Object o = map != null ? map.get(id) : null; if (o == null && parentContext != null) { o = parentContext.getAttribute(id); } return o; } @Override public Object setAttribute(final String id, final Object obj) { if (map == null) { map = new HashMap<>(); } return map.put(id, obj); } @Override public Object removeAttribute(final String id) { return map != null ? map.remove(id) : null; } public T getAttribute(final String id, final Class clazz) { Args.notNull(clazz, "Attribute class"); final Object obj = getAttribute(id); return obj != null ? clazz.cast(obj) : null ; } /** * Represents current request message head. *

* This context attribute is expected to be populated by the protocol handler * in the course of request execution. */ public HttpRequest getRequest() { return request; } /** * @since 5.3 */ @Internal public void setRequest(final HttpRequest request) { this.request = request; } /** * Represents current response message head. *

* This context attribute is expected to be populated by the protocol handler * in the course of request execution. */ public HttpResponse getResponse() { return response; } /** * @since 5.3 */ @Internal public void setResponse(final HttpResponse response) { this.response = response; } /** * Represents current connection endpoint details. *

* This context attribute is expected to be populated by the protocol handler * in the course of request execution. * @since 5.0 */ public EndpointDetails getEndpointDetails() { return endpointDetails; } /** * @since 5.3 */ @Internal public void setEndpointDetails(final EndpointDetails endpointDetails) { this.endpointDetails = endpointDetails; } /** * Represents current TLS session details. *

* This context attribute is expected to be populated by the protocol handler * in the course of request execution. * @since 5.0 */ public SSLSession getSSLSession() { return sslSession; } /** * @since 5.3 */ @Internal public void setSSLSession(final SSLSession sslSession) { this.sslSession = sslSession; } /** * Internal adaptor class that delegates all its method calls to a plain {@link HttpContext}. * To be removed in the future. */ @SuppressWarnings("deprecation") @Internal static class Delegate extends HttpCoreContext { private final HttpContext httpContext; Delegate(final HttpContext httpContext) { super(null); this.httpContext = httpContext; } T getAttr(final String id, final Class clazz) { final Object obj = httpContext.getAttribute(id); if (obj == null) { return null; } return clazz.cast(obj); } @Override public HttpRequest getRequest() { return getAttr(HTTP_REQUEST, HttpRequest.class); } @Override public void setRequest(final HttpRequest request) { httpContext.setAttribute(HTTP_REQUEST, request); } @Override public HttpResponse getResponse() { return getAttr(HTTP_RESPONSE, HttpResponse.class); } @Override public void setResponse(final HttpResponse response) { httpContext.setAttribute(HTTP_RESPONSE, response); } @Override public EndpointDetails getEndpointDetails() { return getAttr(CONNECTION_ENDPOINT, EndpointDetails.class); } @Override public void setEndpointDetails(final EndpointDetails endpointDetails) { httpContext.setAttribute(CONNECTION_ENDPOINT, endpointDetails); } @Override public SSLSession getSSLSession() { return getAttr(SSL_SESSION, SSLSession.class); } @Override public void setSSLSession(final SSLSession sslSession) { httpContext.setAttribute(SSL_SESSION, sslSession); } @Override public ProtocolVersion getProtocolVersion() { return httpContext.getProtocolVersion(); } @Override public void setProtocolVersion(final ProtocolVersion version) { httpContext.setProtocolVersion(version); } @Override public Object getAttribute(final String id) { return httpContext.getAttribute(id); } @Override public Object setAttribute(final String id, final Object obj) { return httpContext.setAttribute(id, obj); } @Override public Object removeAttribute(final String id) { return httpContext.removeAttribute(id); } @Override public T getAttribute(final String id, final Class clazz) { return getAttr(id, clazz); } @Override public String toString() { return httpContext.toString(); } } @Override public String toString() { return "HttpCoreContext{" + "version=" + version + ", request=" + request + ", response=" + response + ", endpointDetails=" + endpointDetails + ", sslSession=" + sslSession + '}'; } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/HttpDateGenerator.java0100644 0000000 0000000 00000006637 00000250600 026636 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.util.TimeZone; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; /** * Generates a date in the format required by the HTTP protocol. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.SAFE) public class HttpDateGenerator { private static final int GRANULARITY_MILLIS = 1000; /** * @deprecated Use {@link #INTERNET_MESSAGE_FORMAT} */ @Deprecated public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz"; public static final String INTERNET_MESSAGE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz"; /** * @deprecated This attribute is no longer supported as a part of the public API. * The time zone to use in the date header. */ @Deprecated public static final TimeZone GMT = TimeZone.getTimeZone("GMT"); public static final ZoneId GMT_ID = ZoneId.of("GMT"); /** Singleton instance. */ public static final HttpDateGenerator INSTANCE = new HttpDateGenerator(INTERNET_MESSAGE_FORMAT, GMT_ID); private final DateTimeFormatter dateTimeFormatter; private long dateAsMillis; private String dateAsText; private ZoneId zoneId; private final ReentrantLock lock; private HttpDateGenerator(final String pattern, final ZoneId zoneId) { dateTimeFormatter = new DateTimeFormatterBuilder() .parseLenient() .parseCaseInsensitive() .appendPattern(pattern) .toFormatter(); this.zoneId = zoneId; this.lock = new ReentrantLock(); } public String getCurrentDate() { lock.lock(); try { final long now = System.currentTimeMillis(); if (now - this.dateAsMillis > GRANULARITY_MILLIS) { // Generate new date string dateAsText = dateTimeFormatter.format(Instant.now().atZone(zoneId)); dateAsMillis = now; } return dateAsText; } finally { lock.unlock(); } } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/HttpProcessor.java0100644 0000000 0000000 00000004603 00000250600 026060 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.HttpResponseInterceptor; /** * Collects protocol interceptors that * implements the 'Chain of Responsibility' pattern, where each individual * protocol interceptor is expected to work on a particular aspect of the HTTP * protocol the interceptor is responsible for. *

* Usually the order in which interceptors are executed should not matter as * long as they do not depend on a particular state of the execution context. * If protocol interceptors have interdependencies and therefore must be * executed in a particular order, they should be added to the protocol * processor in the same sequence as their expected execution order. *

* Protocol interceptors must be implemented as thread-safe. Similarly to * servlets, protocol interceptors should not use instance variables unless * access to those variables is synchronized. * * @since 4.0 */ @Contract(threading = ThreadingBehavior.STATELESS) public interface HttpProcessor extends HttpRequestInterceptor, HttpResponseInterceptor { } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/HttpProcessorBuilder.java0100644 0000000 0000000 00000010776 00000250600 027377 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.HttpResponseInterceptor; /** * Builder for {@link HttpProcessor} instances. * * @since 4.3 */ public class HttpProcessorBuilder { private ChainBuilder requestChainBuilder; private ChainBuilder responseChainBuilder; public static HttpProcessorBuilder create() { return new HttpProcessorBuilder(); } HttpProcessorBuilder() { super(); } private ChainBuilder getRequestChainBuilder() { if (requestChainBuilder == null) { requestChainBuilder = new ChainBuilder<>(); } return requestChainBuilder; } private ChainBuilder getResponseChainBuilder() { if (responseChainBuilder == null) { responseChainBuilder = new ChainBuilder<>(); } return responseChainBuilder; } public HttpProcessorBuilder addFirst(final HttpRequestInterceptor e) { if (e == null) { return this; } getRequestChainBuilder().addFirst(e); return this; } public HttpProcessorBuilder addLast(final HttpRequestInterceptor e) { if (e == null) { return this; } getRequestChainBuilder().addLast(e); return this; } public HttpProcessorBuilder add(final HttpRequestInterceptor e) { return addLast(e); } public HttpProcessorBuilder addAllFirst(final HttpRequestInterceptor... e) { if (e == null) { return this; } getRequestChainBuilder().addAllFirst(e); return this; } public HttpProcessorBuilder addAllLast(final HttpRequestInterceptor... e) { if (e == null) { return this; } getRequestChainBuilder().addAllLast(e); return this; } public HttpProcessorBuilder addAll(final HttpRequestInterceptor... e) { return addAllLast(e); } public HttpProcessorBuilder addFirst(final HttpResponseInterceptor e) { if (e == null) { return this; } getResponseChainBuilder().addFirst(e); return this; } public HttpProcessorBuilder addLast(final HttpResponseInterceptor e) { if (e == null) { return this; } getResponseChainBuilder().addLast(e); return this; } public HttpProcessorBuilder add(final HttpResponseInterceptor e) { return addLast(e); } public HttpProcessorBuilder addAllFirst(final HttpResponseInterceptor... e) { if (e == null) { return this; } getResponseChainBuilder().addAllFirst(e); return this; } public HttpProcessorBuilder addAllLast(final HttpResponseInterceptor... e) { if (e == null) { return this; } getResponseChainBuilder().addAllLast(e); return this; } public HttpProcessorBuilder addAll(final HttpResponseInterceptor... e) { return addAllLast(e); } public HttpProcessor build() { return new DefaultHttpProcessor( requestChainBuilder != null ? requestChainBuilder.build() : null, responseChainBuilder != null ? responseChainBuilder.build() : null); } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/LookupRegistry.java0100644 0000000 0000000 00000004055 00000250600 026244 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; /** * A lookup registry. * * @param The type of objects to register and lookup. * * @deprecated Use {@link org.apache.hc.core5.http.impl.routing.RequestRouter} for * request routing. */ @Deprecated public interface LookupRegistry { /** * Registers the given object for URIs matching the given pattern. * * @param pattern the pattern to register the handler for. * @param obj the object. */ void register(String pattern, T obj); /** * Looks up an object matching the given request path. * * @param value the request path * @return object or {@code null} if no match is found. */ T lookup(String value); /** * Removes registered object, if exists, for the given pattern. * * @param pattern the pattern to unregister. */ void unregister(String pattern); } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestConformance.java0100644 0000000 0000000 00000006754 00000250600 027055 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.MisdirectedRequestException; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TextUtils; /** * This request interceptor is responsible for execution of the protocol conformance * checks on incoming request messages. *

* This interceptor is essential for the HTTP protocol conformance and * the correct operation of the server-side message processing pipeline. *

* * @since 5.3 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class RequestConformance implements HttpRequestInterceptor { public static final RequestConformance INSTANCE = new RequestConformance(); public RequestConformance() { super(); } @Override public void process(final HttpRequest request, final EntityDetails entity, final HttpContext localContext) throws HttpException, IOException { Args.notNull(request, "HTTP request"); if (TextUtils.isBlank(request.getScheme())) { throw new ProtocolException("Request scheme is not set"); } if (TextUtils.isBlank(request.getPath())) { throw new ProtocolException("Request path is not set"); } final URIAuthority authority = request.getAuthority(); if (authority != null && (URIScheme.HTTP.same(request.getScheme()) || URIScheme.HTTPS.same(request.getScheme()))) { final String hostName = authority.getHostName(); if (TextUtils.isBlank(hostName)) { throw new ProtocolException("Request host is empty"); } } final HttpCoreContext context = HttpCoreContext.cast(localContext); if (URIScheme.HTTPS.same(request.getScheme()) && context.getSSLSession() == null) { throw new MisdirectedRequestException("HTTPS request over non-secure connection"); } } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestConnControl.java0100644 0000000 0000000 00000006207 00000250600 027052 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.util.Args; /** * This request interceptor is responsible for adding {@code Connection} header * to outgoing requests, which is essential for managing persistence of * {@code HTTP/1.0} connections. *

* This interceptor is recommended for the HTTP protocol conformance and * the correct operation of the client-side message processing pipeline. *

* * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class RequestConnControl implements HttpRequestInterceptor { /** * Singleton instance. * @since 5.2 */ public static final HttpRequestInterceptor INSTANCE = new RequestConnControl(); public RequestConnControl() { super(); } @Override public void process(final HttpRequest request, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { Args.notNull(request, "HTTP request"); final String method = request.getMethod(); if (Method.CONNECT.isSame(method)) { return; } if (!request.containsHeader(HttpHeaders.CONNECTION)) { // Default policy is to keep connection alive // whenever possible if (request.containsHeader(HttpHeaders.UPGRADE)) { request.addHeader(HttpHeaders.CONNECTION, HeaderElements.UPGRADE); } else { request.addHeader(HttpHeaders.CONNECTION, HeaderElements.KEEP_ALIVE); } } } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestContent.java0100644 0000000 0000000 00000016251 00000250600 026226 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.util.Args; /** * This request interceptor is responsible for delimiting the message content * by adding {@code Content-Length} or {@code Transfer-Content} headers based * on the properties of the enclosed entity and the protocol version. *

* This interceptor is essential for the HTTP protocol conformance and * the correct operation of the client-side message processing pipeline. *

* * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class RequestContent implements HttpRequestInterceptor { /** * Singleton instance. * @since 5.2 */ public static final HttpRequestInterceptor INSTANCE = new RequestContent(); private final boolean overwrite; /** * Default constructor. The {@code Content-Length} or {@code Transfer-Encoding} * will cause the interceptor to throw {@link ProtocolException} if already present in the * response message. */ public RequestContent() { this(false); } /** * Constructor that can be used to fine-tune behavior of this interceptor. * * @param overwrite If set to {@code true} the {@code Content-Length} and * {@code Transfer-Encoding} headers will be created or updated if already present. * If set to {@code false} the {@code Content-Length} and * {@code Transfer-Encoding} headers will cause the interceptor to throw * {@link ProtocolException} if already present in the response message. * * @since 4.2 */ public RequestContent(final boolean overwrite) { super(); this.overwrite = overwrite; } @Override public void process(final HttpRequest request, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { Args.notNull(request, "HTTP request"); final String method = request.getMethod(); if (Method.TRACE.isSame(method) && entity != null) { throw new ProtocolException("TRACE request may not enclose an entity"); } if (this.overwrite) { request.removeHeaders(HttpHeaders.TRANSFER_ENCODING); request.removeHeaders(HttpHeaders.CONTENT_LENGTH); } else { if (request.containsHeader(HttpHeaders.TRANSFER_ENCODING)) { throw new ProtocolException("Transfer-encoding header already present"); } if (request.containsHeader(HttpHeaders.CONTENT_LENGTH)) { throw new ProtocolException("Content-Length header already present"); } } if (entity == null && isContentEnclosingMethod(method)) { request.addHeader(HttpHeaders.CONTENT_LENGTH, "0"); return; } if (entity != null) { // Check for OPTIONS request with content but no Content-Type header validateOptionsContentType(request); final ProtocolVersion ver = context.getProtocolVersion(); // Must specify a transfer encoding or a content length if (entity.isChunked() || entity.getContentLength() < 0) { if (ver.lessEquals(HttpVersion.HTTP_1_0)) { throw new ProtocolException( "Chunked transfer encoding not allowed for " + ver); } request.addHeader(HttpHeaders.TRANSFER_ENCODING, HeaderElements.CHUNKED_ENCODING); MessageSupport.addTrailerHeader(request, entity); } else { request.addHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(entity.getContentLength())); } MessageSupport.addContentTypeHeader(request, entity); MessageSupport.addContentEncodingHeader(request, entity); } } private boolean isContentEnclosingMethod(final String method) { return Method.POST.isSame(method) || Method.PUT.isSame(method) || Method.PATCH.isSame(method) || Method.QUERY.isSame(method); } /** * Validates the presence of the Content-Type header for an OPTIONS request. * *

* According to the RFC specifications, an HTTP {@link Method#OPTIONS} request that contains content * must have a Content-Type header. This method checks for the presence of the Content-Type header * in such requests. It does not validate the actual value of the Content-Type header, as determining * its validity would require knowledge of all valid media types, which is beyond the scope of this method. * If the header is absent, a {@link ProtocolException} is thrown. *

* *

* Note: This method does not check the validity of the Content-Type header value, only its presence. *

* * @param request The {@link HttpRequest} to be validated for the presence of the Content-Type header. Must not be null. * @throws ProtocolException If the Content-Type header is missing in an OPTIONS request with content. */ public void validateOptionsContentType(final HttpRequest request) throws ProtocolException { if (Method.OPTIONS.isSame(request.getMethod())) { final Header header = request.getFirstHeader(HttpHeaders.CONTENT_TYPE); if (header == null) { throw new ProtocolException("OPTIONS request must have Content-Type header"); } } } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestDate.java0100644 0000000 0000000 00000005114 00000250600 025465 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.util.Args; /** * This request interceptor is responsible for adding {@code Date} header * to outgoing request messages. *

* This interceptor is recommended for the HTTP protocol conformance and * the correct operation of the client-side message processing pipeline. *

* * @since 4.0 */ @Contract(threading = ThreadingBehavior.SAFE) public class RequestDate implements HttpRequestInterceptor { /** * Singleton instance. * @since 5.2 */ public static final HttpRequestInterceptor INSTANCE = new RequestDate(); public RequestDate() { super(); } @Override public void process(final HttpRequest request, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { Args.notNull(request, "HTTP request"); if (entity != null && !request.containsHeader(HttpHeaders.DATE)) { request.setHeader(HttpHeaders.DATE, HttpDateGenerator.INSTANCE.getCurrentDate()); } } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestExpectContinue.java0100644 0000000 0000000 00000006142 00000250600 027547 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.util.Args; /** * This request interceptor is responsible for activation of the 'expect-continue' * handshake by adding a {@code Expect} header describing client expectations. *

* This interceptor is recommended for the HTTP protocol conformance and * the correct operation of the client-side message processing pipeline. *

* * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class RequestExpectContinue implements HttpRequestInterceptor { /** * Singleton instance. * * @since 5.2 */ public static final RequestExpectContinue INSTANCE = new RequestExpectContinue(); public RequestExpectContinue() { super(); } @Override public void process(final HttpRequest request, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { Args.notNull(request, "HTTP request"); if (!request.containsHeader(HttpHeaders.EXPECT)) { if (entity != null) { final ProtocolVersion ver = context.getProtocolVersion(); // Do not send the expect header if request body is known to be empty if (entity.getContentLength() != 0 && !ver.lessEquals(HttpVersion.HTTP_1_0)) { request.addHeader(HttpHeaders.EXPECT, HeaderElements.CONTINUE); } } } } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestHandlerRegistry.java0100644 0000000 0000000 00000013077 00000250600 027725 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestMapper; import org.apache.hc.core5.http.MisdirectedRequestException; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TextUtils; /** * Generic registry of request handlers that can be resolved by properties of request messages. * * @param request handler type. * * @since 5.0 * * @deprecated Use {@link org.apache.hc.core5.http.impl.routing.RequestRouter}. */ @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL) @Deprecated public class RequestHandlerRegistry implements HttpRequestMapper { private final static String LOCALHOST = "localhost"; private final static String IP_127_0_0_1 = "127.0.0.1"; private final String canonicalHostName; private final Supplier> registrySupplier; private final LookupRegistry primary; private final ConcurrentMap> virtualMap; public RequestHandlerRegistry(final String canonicalHostName, final Supplier> registrySupplier) { this.canonicalHostName = TextUtils.toLowerCase(Args.notNull(canonicalHostName, "Canonical hostname")); this.registrySupplier = registrySupplier != null ? registrySupplier : UriPatternMatcher::new; this.primary = this.registrySupplier.get(); this.virtualMap = new ConcurrentHashMap<>(); } static LookupRegistry newMatcher(final UriPatternType type) { if (type == null) { return new UriPatternMatcher<>(); } switch (type) { case REGEX: return new UriRegexMatcher<>(); case URI_PATTERN_IN_ORDER: return new UriPatternOrderedMatcher<>(); case URI_PATTERN: default: return new UriPatternMatcher<>(); } } public RequestHandlerRegistry(final String canonicalHostName, final UriPatternType patternType) { this(canonicalHostName, () -> newMatcher(patternType)); } public RequestHandlerRegistry(final UriPatternType patternType) { this(LOCALHOST, patternType); } public RequestHandlerRegistry() { this(LOCALHOST, UriPatternType.URI_PATTERN); } private LookupRegistry getPatternMatcher(final String hostname) { if (hostname == null || hostname.equals(canonicalHostName) || hostname.equals(LOCALHOST) || hostname.equals(IP_127_0_0_1)) { return primary; } return virtualMap.get(hostname); } @Override public T resolve(final HttpRequest request, final HttpContext context) throws MisdirectedRequestException { final URIAuthority authority = request.getAuthority(); final String key = authority != null ? TextUtils.toLowerCase(authority.getHostName()) : null; final LookupRegistry patternMatcher = getPatternMatcher(key); if (patternMatcher == null) { throw new MisdirectedRequestException("Not authoritative"); } String path = request.getPath(); final int i = path.indexOf('?'); if (i != -1) { path = path.substring(0, i); } return patternMatcher.lookup(path); } public void register(final String hostname, final String uriPattern, final T object) { Args.notBlank(uriPattern, "URI pattern"); if (object == null) { return; } final String key = TextUtils.toLowerCase(hostname); if (hostname == null || hostname.equals(canonicalHostName) || hostname.equals(LOCALHOST)) { primary.register(uriPattern, object); } else { LookupRegistry patternMatcher = virtualMap.get(key); if (patternMatcher == null) { final LookupRegistry newPatternMatcher = registrySupplier.get(); patternMatcher = virtualMap.putIfAbsent(key, newPatternMatcher); if (patternMatcher == null) { patternMatcher = newPatternMatcher; } } patternMatcher.register(uriPattern, object); } } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestTE.java0100644 0000000 0000000 00000012233 00000250600 025120 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.io.IOException; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.util.Args; /** * HTTP protocol interceptor responsible for validating and processing the {@link HttpHeaders#TE} header field in HTTP/1.1 requests. *

* The {@link HttpHeaders#TE} header is used to indicate transfer codings the client is willing to accept and, in some cases, whether * the client is willing to accept trailer fields. This interceptor ensures that the {@link HttpHeaders#TE} header does not include * the {@code chunked} transfer coding and validates the presence of the {@code Connection: TE} header. *

* For HTTP/1.1 requests, the {@link HttpHeaders#TE} header can contain multiple values separated by commas and may include quality * values (denoted by {@code q=}) separated by semicolons. *

* In case of HTTP/2, this validation is skipped, and another layer of logic handles the specifics of HTTP/2 compliance. * * @since 5.4 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class RequestTE implements HttpRequestInterceptor { /** * Singleton instance of the {@code RequestTE} interceptor. */ public static final HttpRequestInterceptor INSTANCE = new RequestTE(); /** * Default constructor. */ public RequestTE() { super(); } /** * Processes the {@code TE} header of the given HTTP request and ensures compliance with HTTP/1.1 requirements. *

* If the {@code TE} header is present, this method validates that: *

    *
  • The {@code TE} header does not include the {@code chunked} transfer coding, which is implicitly supported for HTTP/1.1.
  • *
  • The {@code Connection} header includes the {@code TE} directive, as required by the protocol.
  • *
* * @param request the HTTP request containing the headers to validate * @param entity the entity associated with the request (may be {@code null}) * @param context the execution context for the request * @throws HttpException if the {@code TE} header contains invalid values or the {@code Connection} header is missing * @throws IOException in case of an I/O error */ @Override public void process(final HttpRequest request, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { Args.notNull(request, "HTTP request"); final AtomicBoolean hasTE = new AtomicBoolean(false); final AtomicBoolean hasChunk = new AtomicBoolean(false); MessageSupport.parseTokens(request, HttpHeaders.TE, token -> { hasTE.set(true); if (token.equalsIgnoreCase("chunked")) { hasChunk.set(true); } }); if (hasChunk.get()) { throw new ProtocolException("'chunked' transfer coding must not be listed in the TE header for HTTP/1.1."); } if (hasTE.get()) { final AtomicBoolean hasConnection = new AtomicBoolean(false); final AtomicBoolean hasTEinConnection = new AtomicBoolean(false); MessageSupport.parseTokens(request, HttpHeaders.CONNECTION, token -> { hasConnection.set(true); if ("TE".equalsIgnoreCase(token)) { hasTEinConnection.set(true); } }); if (!hasTEinConnection.get()) { throw new ProtocolException("The 'Connection' header must include the 'TE' directive when the 'TE' header is present."); } } } }httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestTargetHost.java0100644 0000000 0000000 00000007014 00000250600 026675 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.util.Args; /** * This request interceptor is responsible for adding {@code Host} header to * outgoing request messages. *

* This interceptor is essential for the HTTP protocol conformance and * the correct operation of the client-side message processing pipeline. *

* @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class RequestTargetHost implements HttpRequestInterceptor { /** * Singleton instance. * @since 5.2 */ public static final HttpRequestInterceptor INSTANCE = new RequestTargetHost(); public RequestTargetHost() { super(); } @SuppressWarnings("deprecation") @Override public void process(final HttpRequest request, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { Args.notNull(request, "HTTP request"); Args.notNull(context, "HTTP context"); final ProtocolVersion ver = context.getProtocolVersion(); final String method = request.getMethod(); if (Method.CONNECT.isSame(method) && ver.lessEquals(HttpVersion.HTTP_1_0)) { return; } if (!request.containsHeader(HttpHeaders.HOST)) { URIAuthority authority = request.getAuthority(); if (authority == null) { if (ver.lessEquals(HttpVersion.HTTP_1_0)) { return; } throw new ProtocolException("Target host is unknown"); } if (authority.getUserInfo() != null) { authority = new URIAuthority(authority.getHostName(), authority.getPort()); } request.addHeader(HttpHeaders.HOST, authority); } } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestUserAgent.java0100644 0000000 0000000 00000005323 00000250600 026507 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.util.Args; /** * This request interceptor is responsible for adding {@code User-Agent} header. *

* This interceptor is recommended for the HTTP protocol conformance and * the correct operation of the client-side message processing pipeline. *

* * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class RequestUserAgent implements HttpRequestInterceptor { /** * Singleton instance. * @since 5.2 */ public static final HttpRequestInterceptor INSTANCE = new RequestUserAgent(); private final String userAgent; public RequestUserAgent(final String userAgent) { super(); this.userAgent = userAgent; } public RequestUserAgent() { this(null); } @Override public void process(final HttpRequest request, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { Args.notNull(request, "HTTP request"); if (!request.containsHeader(HttpHeaders.USER_AGENT) && this.userAgent != null) { request.addHeader(HttpHeaders.USER_AGENT, this.userAgent); } } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestValidateHost.java0100644 0000000 0000000 00000007020 00000250600 027175 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.io.IOException; import java.net.URISyntaxException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.util.Args; /** * This request interceptor is responsible for copying {@code Host} header value to * {@link HttpRequest#setAuthority(URIAuthority)} of incoming request messages. *

* This interceptor is essential for the HTTP protocol conformance and * the correct operation of the server-side message processing pipeline. *

* * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class RequestValidateHost implements HttpRequestInterceptor { public static final RequestValidateHost INSTANCE = new RequestValidateHost(); public RequestValidateHost() { super(); } @Override public void process(final HttpRequest request, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { Args.notNull(request, "HTTP request"); // Request authority already specified (likely in an absolute request URI) if (request.getAuthority() != null) { return; } final Header header = request.getHeader(HttpHeaders.HOST); if (header != null) { final URIAuthority authority; try { authority = URIAuthority.create(header.getValue()); } catch (final URISyntaxException ex) { throw new ProtocolException(ex.getMessage(), ex); } request.setAuthority(authority); } else { final ProtocolVersion version = request.getVersion() != null ? request.getVersion() : HttpVersion.HTTP_1_1; if (version.greaterEquals(HttpVersion.HTTP_1_1)) { throw new ProtocolException("Host header is absent"); } } } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseConformance.java0100644 0000000 0000000 00000005405 00000250600 027213 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponseInterceptor; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.util.Args; /** * This response interceptor is responsible for making the protocol conformance checks * of outgoing response messages. *

* This interceptor is essential for the HTTP protocol conformance and * the correct operation of the server-side message processing pipeline. *

* * @since 5.3 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class ResponseConformance implements HttpResponseInterceptor { public static final ResponseConformance INSTANCE = new ResponseConformance(); public ResponseConformance() { super(); } @Override public void process(final HttpResponse response, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { Args.notNull(response, "HTTP response"); final int status = response.getCode(); switch (status) { case HttpStatus.SC_NO_CONTENT: case HttpStatus.SC_NOT_MODIFIED: if (entity != null) { throw new ProtocolException("Response " + status + " must not enclose an entity"); } } } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseConnControl.java0100644 0000000 0000000 00000013153 00000250600 027216 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.io.IOException; import java.util.Iterator; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HeaderElement; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponseInterceptor; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.util.Args; /** * This response interceptor is responsible for adding {@code Connection} header * to outgoing responses, which is essential for managing persistence of * {@code HTTP/1.0} connections. *

* This interceptor is recommended for the HTTP protocol conformance and * the correct operation of the server-side message processing pipeline. *

* * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class ResponseConnControl implements HttpResponseInterceptor { public static final ResponseConnControl INSTANCE = new ResponseConnControl(); public ResponseConnControl() { super(); } @Override public void process(final HttpResponse response, final EntityDetails entity, final HttpContext localContext) throws HttpException, IOException { Args.notNull(response, "HTTP response"); Args.notNull(localContext, "HTTP context"); final HttpCoreContext context = HttpCoreContext.cast(localContext); // Always drop connection after certain type of responses final int status = response.getCode(); if (status == HttpStatus.SC_BAD_REQUEST || status == HttpStatus.SC_REQUEST_TIMEOUT || status == HttpStatus.SC_LENGTH_REQUIRED || status == HttpStatus.SC_REQUEST_TOO_LONG || status == HttpStatus.SC_REQUEST_URI_TOO_LONG || status == HttpStatus.SC_SERVICE_UNAVAILABLE || status == HttpStatus.SC_NOT_IMPLEMENTED) { response.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); return; } if (!response.containsHeader(HttpHeaders.CONNECTION)) { // Always drop connection for HTTP/1.0 responses and below // if the content body cannot be correctly delimited final ProtocolVersion ver = context.getProtocolVersion(); if (entity != null && entity.getContentLength() < 0 && ver.lessEquals(HttpVersion.HTTP_1_0)) { response.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); } else { final HttpRequest request = context.getRequest(); boolean closeRequested = false; boolean keepAliveRequested = false; if (request != null) { final Iterator it = MessageSupport.iterate(request, HttpHeaders.CONNECTION); while (it.hasNext()) { final HeaderElement he = it.next(); if (he.getName().equalsIgnoreCase(HeaderElements.CLOSE)) { closeRequested = true; break; } else if (he.getName().equalsIgnoreCase(HeaderElements.KEEP_ALIVE)) { keepAliveRequested = true; } } } if (closeRequested) { response.addHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); } else { if (response.containsHeader(HttpHeaders.UPGRADE)) { response.addHeader(HttpHeaders.CONNECTION, HeaderElements.UPGRADE); } else { if (keepAliveRequested) { response.addHeader(HttpHeaders.CONNECTION, HeaderElements.KEEP_ALIVE); } else { if (ver.lessEquals(HttpVersion.HTTP_1_0)) { response.addHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); } } } } } } } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseContent.java0100644 0000000 0000000 00000012702 00000250600 026371 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponseInterceptor; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.util.Args; /** * This response interceptor is responsible for delimiting the message content * by adding {@code Content-Length} or {@code Transfer-Content} headers based * on the properties of the enclosed entity and the protocol version. *

* This interceptor is essential for the HTTP protocol conformance and * the correct operation of the server-side message processing pipeline. *

* * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class ResponseContent implements HttpResponseInterceptor { public static final ResponseContent INSTANCE = new ResponseContent(); private final boolean overwrite; /** * Default constructor. The {@code Content-Length} or {@code Transfer-Encoding} * will cause the interceptor to throw {@link ProtocolException} if already present in the * response message. */ public ResponseContent() { this(false); } /** * Constructor that can be used to fine-tune behavior of this interceptor. * * @param overwrite If set to {@code true} the {@code Content-Length} and * {@code Transfer-Encoding} headers will be created or updated if already present. * If set to {@code false} the {@code Content-Length} and * {@code Transfer-Encoding} headers will cause the interceptor to throw * {@link ProtocolException} if already present in the response message. * * @since 4.2 */ public ResponseContent(final boolean overwrite) { super(); this.overwrite = overwrite; } /** * Processes the response (possibly updating or inserting) Content-Length and Transfer-Encoding headers. * @param response The HttpResponse to modify. * @param context Unused. * @throws ProtocolException If either the Content-Length or Transfer-Encoding headers are found. * @throws IllegalArgumentException If the response is null. */ @Override public void process(final HttpResponse response, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { Args.notNull(response, "HTTP response"); if (this.overwrite) { response.removeHeaders(HttpHeaders.TRANSFER_ENCODING); response.removeHeaders(HttpHeaders.CONTENT_LENGTH); } else { if (response.containsHeader(HttpHeaders.TRANSFER_ENCODING)) { throw new ProtocolException("Transfer-encoding header already present"); } if (response.containsHeader(HttpHeaders.CONTENT_LENGTH)) { throw new ProtocolException("Content-Length header already present"); } } final ProtocolVersion ver = context.getProtocolVersion(); if (entity != null) { final long len = entity.getContentLength(); if (len >= 0 && !entity.isChunked()) { response.addHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(entity.getContentLength())); } else if (ver.greaterEquals(HttpVersion.HTTP_1_1)) { response.addHeader(HttpHeaders.TRANSFER_ENCODING, HeaderElements.CHUNKED_ENCODING); MessageSupport.addTrailerHeader(response, entity); } MessageSupport.addContentTypeHeader(response, entity); MessageSupport.addContentEncodingHeader(response, entity); } else { final int status = response.getCode(); if (status != HttpStatus.SC_NO_CONTENT && status != HttpStatus.SC_NOT_MODIFIED) { response.addHeader(HttpHeaders.CONTENT_LENGTH, "0"); } } } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseDate.java0100644 0000000 0000000 00000006552 00000250600 025642 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponseInterceptor; import org.apache.hc.core5.util.Args; /** * This response interceptor is responsible for adding {@code Date} header * to outgoing response messages. *

* This interceptor is recommended for the HTTP protocol conformance and * the correct operation of the server-side message processing pipeline. *

*

* If the {@code Date} header is missing or considered invalid, and the * {@code alwaysReplace} flag is set to {@code true}, the interceptor will replace it * with the current system date and time. *

* * @since 4.0 */ @Contract(threading = ThreadingBehavior.SAFE) public class ResponseDate implements HttpResponseInterceptor { /** * Indicates whether to always replace an invalid or missing {@code Date} header. * * @since 5.4 */ private final boolean alwaysReplace; public static final ResponseDate INSTANCE = new ResponseDate(); public ResponseDate() { this(false); } /** * Constructs a ResponseDate interceptor. * * @param alwaysReplace Whether to replace an invalid {@code Date} header. * If {@code true}, the interceptor will replace any * detected invalid {@code Date} header with a valid value. * @since 5.4 */ public ResponseDate(final boolean alwaysReplace) { super(); this.alwaysReplace = alwaysReplace; } @Override public void process(final HttpResponse response, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { Args.notNull(response, "HTTP response"); if (alwaysReplace || response.getFirstHeader(HttpHeaders.DATE) == null) { response.setHeader(HttpHeaders.DATE, HttpDateGenerator.INSTANCE.getCurrentDate()); } } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseServer.java0100644 0000000 0000000 00000005163 00000250600 026230 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponseInterceptor; import org.apache.hc.core5.util.Args; /** * This response interceptor is responsible for adding {@code Server} header. *

* This interceptor is recommended for the HTTP protocol conformance and * the correct operation of the server-side message processing pipeline. *

* * @since 4.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class ResponseServer implements HttpResponseInterceptor { private final String originServer; /** * @since 4.3 */ public ResponseServer(final String originServer) { super(); this.originServer = originServer; } public ResponseServer() { this(null); } @Override public void process(final HttpResponse response, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { Args.notNull(response, "HTTP response"); if (!response.containsHeader(HttpHeaders.SERVER) && this.originServer != null) { response.addHeader(HttpHeaders.SERVER, this.originServer); } } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriPatternMatcher.java0100644 0000000 0000000 00000012625 00000250600 026645 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.impl.routing.PathPatternMatcher; import org.apache.hc.core5.util.Args; /** * Maintains a map of objects keyed by a request URI pattern. *

* Patterns may have three formats: *

*
    *
  • {@code *}
  • *
  • {@code *}
  • *
  • {@code *}
  • *
*

* This class can be used to resolve an object matching a particular request * URI. *

* * @param The type of registered objects. * @since 4.0 * * @deprecated Use {@link org.apache.hc.core5.http.impl.routing.RequestRouter} for * request routing. */ @Contract(threading = ThreadingBehavior.SAFE) @Deprecated public class UriPatternMatcher implements LookupRegistry { private final Map map; private final ReentrantLock lock; public UriPatternMatcher() { super(); this.map = new LinkedHashMap<>(); this.lock = new ReentrantLock(); } /** * Returns a {@link Set} view of the mappings contained in this matcher. * * @return a set view of the mappings contained in this matcher. * * @see Map#entrySet() * @since 4.4.9 */ public Set> entrySet() { lock.lock(); try { return new HashSet<>(map.entrySet()); } finally { lock.unlock(); } } /** * Registers the given object for URIs matching the given pattern. * * @param pattern * the pattern to register the handler for. * @param obj * the object. */ @Override public void register(final String pattern, final T obj) { lock.lock(); try { Args.notNull(pattern, "URI request pattern"); this.map.put(pattern, obj); } finally { lock.unlock(); } } /** * Removes registered object, if exists, for the given pattern. * * @param pattern * the pattern to unregister. */ @Override public void unregister(final String pattern) { lock.lock(); try { if (pattern == null) { return; } this.map.remove(pattern); } finally { lock.unlock(); } } /** * Looks up an object matching the given request path. * * @param path * the request path * @return object or {@code null} if no match is found. */ @Override public T lookup(final String path) { lock.lock(); try { Args.notNull(path, "Request path"); // direct match? T obj = this.map.get(path); if (obj == null) { // pattern match? String bestMatch = null; for (final String pattern : this.map.keySet()) { if (matchUriRequestPattern(pattern, path)) { // we have a match. is it any better? if (bestMatch == null || bestMatch.length() < pattern.length() || bestMatch.length() == pattern.length() && pattern.endsWith("*")) { obj = this.map.get(pattern); bestMatch = pattern; } } } } return obj; } finally { lock.unlock(); } } /** * Tests if the given request path matches the given pattern. * * @param pattern * the pattern * @param path * the request path * @return {@code true} if the request URI matches the pattern, {@code false} otherwise. */ protected boolean matchUriRequestPattern(final String pattern, final String path) { return PathPatternMatcher.INSTANCE.match(pattern, path); } @Override public String toString() { return this.map.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriPatternOrderedMatcher.java0100644 0000000 0000000 00000011654 00000250600 030153 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.impl.routing.PathPatternMatcher; import org.apache.hc.core5.util.Args; /** * Maintains a map of objects keyed by a request URI pattern. *

* Patterns may have three formats: *

*
    *
  • {@code *}
  • *
  • {@code *}
  • *
  • {@code *}
  • *
*

* This class can be used to resolve an object matching a particular request * URI. *

* * @param The type of registered objects. * @since 5.0 * * @deprecated Use {@link org.apache.hc.core5.http.impl.routing.RequestRouter} for * request routing. */ @Contract(threading = ThreadingBehavior.SAFE) @Deprecated public class UriPatternOrderedMatcher implements LookupRegistry { private final Map map; private final ReentrantLock lock = new ReentrantLock(); public UriPatternOrderedMatcher() { super(); this.map = new LinkedHashMap<>(); } /** * Returns a {@link Set} view of the mappings contained in this matcher. * * @return a set view of the mappings contained in this matcher. * * @see Map#entrySet() * @since 4.4.9 */ public Set> entrySet() { lock.lock(); try { return new HashSet<>(map.entrySet()); } finally { lock.unlock(); } } /** * Registers the given object for URIs matching the given pattern. * * @param pattern the pattern to register the handler for. * @param obj the object. */ @Override public void register(final String pattern, final T obj) { lock.lock(); try { Args.notNull(pattern, "URI request pattern"); this.map.put(pattern, obj); } finally { lock.unlock(); } } /** * Removes registered object, if exists, for the given pattern. * * @param pattern the pattern to unregister. */ @Override public void unregister(final String pattern) { lock.lock(); try { if (pattern == null) { return; } this.map.remove(pattern); } finally { lock.unlock(); } } /** * Looks up an object matching the given request path. * * @param path the request path * @return object or {@code null} if no match is found. */ @Override public T lookup(final String path) { lock.lock(); try { Args.notNull(path, "Request path"); for (final Entry entry : this.map.entrySet()) { final String pattern = entry.getKey(); if (path.equals(pattern)) { return entry.getValue(); } if (matchUriRequestPattern(pattern, path)) { return this.map.get(pattern); } } return null; } finally { lock.unlock(); } } /** * Tests if the given request path matches the given pattern. * * @param pattern the pattern * @param path the request path * @return {@code true} if the request URI matches the pattern, {@code false} * otherwise. */ protected boolean matchUriRequestPattern(final String pattern, final String path) { return PathPatternMatcher.INSTANCE.match(pattern, path); } @Override public String toString() { return this.map.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriPatternType.java0100644 0000000 0000000 00000003457 00000250600 026206 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; /** * @since 5.0 */ public enum UriPatternType { REGEX, URI_PATTERN, URI_PATTERN_IN_ORDER; /** * @deprecated Do not use. */ @Deprecated public static LookupRegistry newMatcher(final UriPatternType type) { if (type == null) { return new UriPatternMatcher<>(); } switch (type) { case REGEX: return new UriRegexMatcher<>(); case URI_PATTERN_IN_ORDER: return new UriPatternOrderedMatcher<>(); case URI_PATTERN: default: return new UriPatternMatcher<>(); } } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriRegexMatcher.java0100644 0000000 0000000 00000010467 00000250600 026304 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.locks.ReentrantLock; import java.util.regex.Pattern; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.util.Args; /** * Maintains a map of objects keyed by a request URI regular expression. * *

* The insertion order is in maintained in that map such that the lookup tests each regex until there is a match. This * class can be used to resolve an object matching a particular request URI. *

* * @param The type of registered objects. * @since 5.0 * * @deprecated Use {@link org.apache.hc.core5.http.impl.routing.RequestRouter} for * request routing. */ @Contract(threading = ThreadingBehavior.SAFE) @Deprecated public class UriRegexMatcher implements LookupRegistry { private final Map objectMap; private final Map patternMap; private final ReentrantLock lock; public UriRegexMatcher() { super(); this.objectMap = new LinkedHashMap<>(); this.patternMap = new LinkedHashMap<>(); this.lock = new ReentrantLock(); } /** * Registers the given object for URIs matching the given regex. * * @param regex * the regex to register the handler for. * @param obj * the object. */ @Override public void register(final String regex, final T obj) { lock.lock(); try { Args.notNull(regex, "URI request regex"); this.objectMap.put(regex, obj); this.patternMap.put(regex, Pattern.compile(regex)); } finally { lock.unlock(); } } /** * Removes registered object, if exists, for the given regex. * * @param regex * the regex to unregister. */ @Override public void unregister(final String regex) { lock.lock(); try { if (regex == null) { return; } this.objectMap.remove(regex); this.patternMap.remove(regex); } finally { lock.unlock(); } } /** * Looks up an object matching the given request path. * * @param path * the request path * @return object or {@code null} if no match is found. */ @Override public T lookup(final String path) { lock.lock(); try { Args.notNull(path, "Request path"); // direct match? final T obj = this.objectMap.get(path); if (obj == null) { // regex match? for (final Entry entry : this.patternMap.entrySet()) { if (entry.getValue().matcher(path).matches()) { return objectMap.get(entry.getKey()); } } } return obj; } finally { lock.unlock(); } } @Override public String toString() { return this.objectMap.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ViaRequest.java0100644 0000000 0000000 00000013521 00000250600 025330 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.util.Args; /** * This request interceptor can be used by an HTTP proxy or intemediary to add the * {@link HttpHeaders#VIA} HTTP header to outgoing request messages. *

The {@link HttpHeaders#VIA} header is used to indicate intermediate protocols and recipients * between the user agent and the server (on requests) or between the origin server and the client * (on responses). It can be used for tracking message forwards, avoiding request loops, and * identifying the protocol capabilities of senders along the request/response chain. Each member of * the {@link HttpHeaders#VIA} header field value represents a proxy or gateway that has forwarded * the message. *

A proxy MUST send an appropriate {@link HttpHeaders#VIA} header field, as described * in * the HTTP specification, in each message that it forwards. An HTTP-to-HTTP gateway MUST * send an appropriate {@link HttpHeaders#VIA} header field in each inbound request message and * MAY send a {@link HttpHeaders#VIA} header field in forwarded response messages. *

This interceptor ensures that the {@link HttpHeaders#VIA} header is added to the request * only * if it has not been added previously, as per the HTTP specification. Additionally, it updates the * values in the {@link HttpHeaders#VIA} header correctly in case of multiple intermediate protocols * or recipients, by appending its own information about how the message was received to the end of * the header field value. * * @since 5.3 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class ViaRequest implements HttpRequestInterceptor { /** * Singleton instance. */ public static final HttpRequestInterceptor INSTANCE = new ViaRequest(); /** * Constructs a new {@code ViaRequest}. */ public ViaRequest() { } /** * Adds the HTTP {@link HttpHeaders#VIA} header to the request if it does not already exist. * *

This method ensures that the version of the request is HTTP/1.1 or higher, and adds the * Via header in the format {@code }, where {@code } is the protocol name, * {@code } is the major and minor version of the request, and {@code } is the value of the Host header. * *

In case the {@link HttpHeaders#VIA} header already exists, this method updates its value by appending * the new protocol information in the same format. * *

If the version of the request is lower than {@code HTTP/1.1} or the request authority not being specified, * this method throws a {@link ProtocolException}. * * @param request the request object to modify * @param entity the entity for the request, may be {@code null} * @param context the context for the request * @throws ProtocolException if there was a protocol error, such as the request version being lower than {@code HTTP/1.1}, * or the request authority not being specified * @throws IOException if there was an I/O error */ @Override public void process(final HttpRequest request, final EntityDetails entity, final HttpContext context) throws ProtocolException, IOException { Args.notNull(request, "HTTP request"); Args.notNull(context, "HTTP context"); final ProtocolVersion ver = context.getProtocolVersion() != null ? context.getProtocolVersion() : HttpVersion.HTTP_1_1; final URIAuthority authority = request.getAuthority(); if (authority == null) { throw new ProtocolException("Request authority not specified"); } if (!ver.greaterEquals(HttpVersion.HTTP_1_1)) { throw new ProtocolException("Invalid protocol version: %s", ver); } if (!request.containsHeader(HttpHeaders.VIA)) { String viaHeaderValue = ver.getProtocol() + " " + ver.getMajor() + "." + ver.getMinor() + " " + authority.getHostName(); final int port = authority.getPort(); if (port != -1) { viaHeaderValue += ":" + port; } request.addHeader(HttpHeaders.VIA, viaHeaderValue); } } } httpcore5/src/main/java/org/apache/hc/core5/http/protocol/package-info.java0100644 0000000 0000000 00000002370 00000250600 025564 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Core HTTP protocol interceptors. */ package org.apache.hc.core5.http.protocol; httpcore5/src/main/java/org/apache/hc/core5/http/ssl/0040755 0000000 0000000 00000000000 00000250600 021336 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/ssl/TLS.java0100644 0000000 0000000 00000012470 00000250600 022644 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.ssl; import java.util.ArrayList; import java.util.List; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.ProtocolVersionParser; import org.apache.hc.core5.util.Tokenizer; /** * Supported {@code TLS} protocol versions. * * @since 5.0 */ public enum TLS { V_1_0("TLSv1", new ProtocolVersion("TLS", 1, 0)), V_1_1("TLSv1.1", new ProtocolVersion("TLS", 1, 1)), V_1_2("TLSv1.2", new ProtocolVersion("TLS", 1, 2)), V_1_3("TLSv1.3", new ProtocolVersion("TLS", 1, 3)); public final String id; public final ProtocolVersion version; TLS(final String id, final ProtocolVersion version) { this.id = id; this.version = version; } public boolean isSame(final ProtocolVersion protocolVersion) { return version.equals(protocolVersion); } public boolean isComparable(final ProtocolVersion protocolVersion) { return version.isComparable(protocolVersion); } /** * Gets the ID. * @return the ID. * * @since 5.2 */ public String getId() { return id; } /** * Gets the version. * @return the version. * * @since 5.2 */ public ProtocolVersion getVersion() { return version; } public boolean greaterEquals(final ProtocolVersion protocolVersion) { return version.greaterEquals(protocolVersion); } public boolean lessEquals(final ProtocolVersion protocolVersion) { return version.lessEquals(protocolVersion); } @Internal public static ProtocolVersion parse( final CharSequence buffer, final Tokenizer.Cursor cursor, final Tokenizer.Delimiter delimiterPredicate) throws ParseException { final int lowerBound = cursor.getLowerBound(); final int upperBound = cursor.getUpperBound(); int pos = cursor.getPos(); if (pos + 4 > cursor.getUpperBound()) { throw new ParseException("Invalid TLS protocol version", buffer, lowerBound, upperBound, pos); } if (buffer.charAt(pos) != 'T' || buffer.charAt(pos + 1) != 'L' || buffer.charAt(pos + 2) != 'S' || buffer.charAt(pos + 3) != 'v') { throw new ParseException("Invalid TLS protocol version", buffer, lowerBound, upperBound, pos); } pos = pos + 4; cursor.updatePos(pos); if (cursor.atEnd()) { throw new ParseException("Invalid TLS version", buffer, lowerBound, upperBound, pos); } return ProtocolVersionParser.INSTANCE.parse("TLS", null, buffer, cursor, delimiterPredicate); } public static ProtocolVersion parse(final String s) throws ParseException { if (s == null) { return null; } final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); final ProtocolVersion protocolVersion = parse(s, cursor, null); Tokenizer.INSTANCE.skipWhiteSpace(s, cursor); if (!cursor.atEnd()) { throw new ParseException("Invalid TLS protocol version; trailing content"); } return protocolVersion; } public static String[] excludeWeak(final String... protocols) { if (protocols == null) { return null; } final List enabledProtocols = new ArrayList<>(); for (final String protocol : protocols) { if (isSecure(protocol)) { enabledProtocols.add(protocol); } } if (enabledProtocols.isEmpty()) { enabledProtocols.add(V_1_2.id); } return enabledProtocols.toArray(new String[0]); } /** * Tests if a given protocol is considered secure and is enabled by default. * * @param protocol The value to test. * @return {@code true} if the given protocol is secure and enabled, otherwise return {@code * false}. * @since 5.2 */ public static boolean isSecure(final String protocol) { return !protocol.startsWith("SSL") && !protocol.equals(V_1_0.id) && !protocol.equals(V_1_1.id); } } httpcore5/src/main/java/org/apache/hc/core5/http/ssl/TlsCiphers.java0100644 0000000 0000000 00000046101 00000250600 024260 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.ssl; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; /** * TLS cipher suite support methods * * @since 5.0 */ public final class TlsCiphers { private final static Set H2_BLACKLISTED = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( "TLS_NULL_WITH_NULL_NULL", "TLS_RSA_WITH_NULL_MD5", "TLS_RSA_WITH_NULL_SHA", "TLS_RSA_EXPORT_WITH_RC4_40_MD5", "TLS_RSA_WITH_RC4_128_MD5", "TLS_RSA_WITH_RC4_128_SHA", "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", "TLS_RSA_WITH_IDEA_CBC_SHA", "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", "TLS_RSA_WITH_DES_CBC_SHA", "TLS_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", "TLS_DH_DSS_WITH_DES_CBC_SHA", "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", "TLS_DH_RSA_WITH_DES_CBC_SHA", "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", "TLS_DHE_DSS_WITH_DES_CBC_SHA", "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", "TLS_DHE_RSA_WITH_DES_CBC_SHA", "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", "TLS_DH_anon_WITH_RC4_128_MD5", "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", "TLS_DH_anon_WITH_DES_CBC_SHA", "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", "TLS_KRB5_WITH_DES_CBC_SHA", "TLS_KRB5_WITH_3DES_EDE_CBC_SHA", "TLS_KRB5_WITH_RC4_128_SHA", "TLS_KRB5_WITH_IDEA_CBC_SHA", "TLS_KRB5_WITH_DES_CBC_MD5", "TLS_KRB5_WITH_3DES_EDE_CBC_MD5", "TLS_KRB5_WITH_RC4_128_MD5", "TLS_KRB5_WITH_IDEA_CBC_MD5", "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA", "TLS_KRB5_EXPORT_WITH_RC4_40_SHA", "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5", "TLS_KRB5_EXPORT_WITH_RC4_40_MD5", "TLS_PSK_WITH_NULL_SHA", "TLS_DHE_PSK_WITH_NULL_SHA", "TLS_RSA_PSK_WITH_NULL_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_DH_DSS_WITH_AES_128_CBC_SHA", "TLS_DH_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_DH_anon_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA", "TLS_DH_DSS_WITH_AES_256_CBC_SHA", "TLS_DH_RSA_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", "TLS_DH_anon_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_NULL_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA256", "TLS_DH_DSS_WITH_AES_128_CBC_SHA256", "TLS_DH_RSA_WITH_AES_128_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_DH_DSS_WITH_AES_256_CBC_SHA256", "TLS_DH_RSA_WITH_AES_256_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", "TLS_DH_anon_WITH_AES_128_CBC_SHA256", "TLS_DH_anon_WITH_AES_256_CBC_SHA256", "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", "TLS_PSK_WITH_RC4_128_SHA", "TLS_PSK_WITH_3DES_EDE_CBC_SHA", "TLS_PSK_WITH_AES_128_CBC_SHA", "TLS_PSK_WITH_AES_256_CBC_SHA", "TLS_DHE_PSK_WITH_RC4_128_SHA", "TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_PSK_WITH_AES_128_CBC_SHA", "TLS_DHE_PSK_WITH_AES_256_CBC_SHA", "TLS_RSA_PSK_WITH_RC4_128_SHA", "TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_PSK_WITH_AES_128_CBC_SHA", "TLS_RSA_PSK_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_SEED_CBC_SHA", "TLS_DH_DSS_WITH_SEED_CBC_SHA", "TLS_DH_RSA_WITH_SEED_CBC_SHA", "TLS_DHE_DSS_WITH_SEED_CBC_SHA", "TLS_DHE_RSA_WITH_SEED_CBC_SHA", "TLS_DH_anon_WITH_SEED_CBC_SHA", "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_GCM_SHA384", "TLS_DH_RSA_WITH_AES_128_GCM_SHA256", "TLS_DH_RSA_WITH_AES_256_GCM_SHA384", "TLS_DH_DSS_WITH_AES_128_GCM_SHA256", "TLS_DH_DSS_WITH_AES_256_GCM_SHA384", "TLS_DH_anon_WITH_AES_128_GCM_SHA256", "TLS_DH_anon_WITH_AES_256_GCM_SHA384", "TLS_PSK_WITH_AES_128_GCM_SHA256", "TLS_PSK_WITH_AES_256_GCM_SHA384", "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256", "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384", "TLS_PSK_WITH_AES_128_CBC_SHA256", "TLS_PSK_WITH_AES_256_CBC_SHA384", "TLS_PSK_WITH_NULL_SHA256", "TLS_PSK_WITH_NULL_SHA384", "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256", "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384", "TLS_DHE_PSK_WITH_NULL_SHA256", "TLS_DHE_PSK_WITH_NULL_SHA384", "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256", "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384", "TLS_RSA_PSK_WITH_NULL_SHA256", "TLS_RSA_PSK_WITH_NULL_SHA384", "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256", "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256", "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256", "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256", "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", "TLS_EMPTY_RENEGOTIATION_INFO_SCSV", "TLS_ECDH_ECDSA_WITH_NULL_SHA", "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_NULL_SHA", "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDH_RSA_WITH_NULL_SHA", "TLS_ECDH_RSA_WITH_RC4_128_SHA", "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_NULL_SHA", "TLS_ECDHE_RSA_WITH_RC4_128_SHA", "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDH_anon_WITH_NULL_SHA", "TLS_ECDH_anon_WITH_RC4_128_SHA", "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", "TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA", "TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_SRP_SHA_WITH_AES_128_CBC_SHA", "TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA", "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA", "TLS_SRP_SHA_WITH_AES_256_CBC_SHA", "TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA", "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_PSK_WITH_RC4_128_SHA", "TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA", "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA", "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA", "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_PSK_WITH_NULL_SHA", "TLS_ECDHE_PSK_WITH_NULL_SHA256", "TLS_ECDHE_PSK_WITH_NULL_SHA384", "TLS_RSA_WITH_ARIA_128_CBC_SHA256", "TLS_RSA_WITH_ARIA_256_CBC_SHA384", "TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256", "TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384", "TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256", "TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384", "TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256", "TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384", "TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256", "TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384", "TLS_DH_anon_WITH_ARIA_128_CBC_SHA256", "TLS_DH_anon_WITH_ARIA_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384", "TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256", "TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384", "TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256", "TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384", "TLS_RSA_WITH_ARIA_128_GCM_SHA256", "TLS_RSA_WITH_ARIA_256_GCM_SHA384", "TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256", "TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384", "TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256", "TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384", "TLS_DH_anon_WITH_ARIA_128_GCM_SHA256", "TLS_DH_anon_WITH_ARIA_256_GCM_SHA384", "TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256", "TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384", "TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256", "TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384", "TLS_PSK_WITH_ARIA_128_CBC_SHA256", "TLS_PSK_WITH_ARIA_256_CBC_SHA384", "TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256", "TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384", "TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256", "TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384", "TLS_PSK_WITH_ARIA_128_GCM_SHA256", "TLS_PSK_WITH_ARIA_256_GCM_SHA384", "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256", "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384", "TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256", "TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", "TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256", "TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384", "TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256", "TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384", "TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256", "TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384", "TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256", "TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384", "TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256", "TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384", "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", "TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256", "TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384", "TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256", "TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384", "TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256", "TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384", "TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256", "TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384", "TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", "TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", "TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256", "TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384", "TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", "TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", "TLS_RSA_WITH_AES_128_CCM", "TLS_RSA_WITH_AES_256_CCM", "TLS_RSA_WITH_AES_128_CCM_8", "TLS_RSA_WITH_AES_256_CCM_8", "TLS_PSK_WITH_AES_128_CCM", "TLS_PSK_WITH_AES_256_CCM", "TLS_PSK_WITH_AES_128_CCM_8", "TLS_PSK_WITH_AES_256_CCM_8" ))); public static boolean isH2Blacklisted(final String cipherSuite) { return H2_BLACKLISTED.contains(cipherSuite); } private static final String WEAK_KEY_EXCHANGES = "^(TLS|SSL)_(NULL|ECDH_anon|DH_anon|DH_anon_EXPORT|DHE_RSA_EXPORT|DHE_DSS_EXPORT|" + "DSS_EXPORT|DH_DSS_EXPORT|DH_RSA_EXPORT|RSA_EXPORT|KRB5_EXPORT)_(.*)"; private static final String WEAK_CIPHERS = "^(TLS|SSL)_(.*)_WITH_(NULL|DES_CBC|DES40_CBC|DES_CBC_40|3DES_EDE_CBC|RC4_128|RC4_40|RC2_CBC_40)_(.*)"; private static final List WEAK_CIPHER_SUITE_PATTERNS = Collections.unmodifiableList(Arrays.asList( Pattern.compile(WEAK_KEY_EXCHANGES, Pattern.CASE_INSENSITIVE), Pattern.compile(WEAK_CIPHERS, Pattern.CASE_INSENSITIVE))); public static boolean isWeak(final String cipherSuite) { for (final Pattern pattern : WEAK_CIPHER_SUITE_PATTERNS) { if (pattern.matcher(cipherSuite).matches()) { return true; } } return false; } public static String[] excludeH2Blacklisted(final String... ciphers) { if (ciphers == null) { return null; } final List enabledCiphers = new ArrayList<>(); for (final String cipher: ciphers) { if (!TlsCiphers.isH2Blacklisted(cipher)) { enabledCiphers.add(cipher); } } return !enabledCiphers.isEmpty() ? enabledCiphers.toArray(new String[0]) : ciphers; } public static String[] excludeWeak(final String... ciphers) { if (ciphers == null) { return null; } final List enabledCiphers = new ArrayList<>(); for (final String cipher: ciphers) { if (!TlsCiphers.isWeak(cipher)) { enabledCiphers.add(cipher); } } return !enabledCiphers.isEmpty() ? enabledCiphers.toArray(new String[0]) : ciphers; } } httpcore5/src/main/java/org/apache/hc/core5/http/support/0040755 0000000 0000000 00000000000 00000250600 022251 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/http/support/AbstractMessageBuilder.java0100644 0000000 0000000 00000011745 00000250600 027500 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.support; import java.util.Iterator; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpMessage; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.HeaderGroup; /** * Abstract {@link HttpMessage} builder. * * @param The message type to build. * @since 5.1 */ public abstract class AbstractMessageBuilder { private ProtocolVersion version; private HeaderGroup headerGroup; protected AbstractMessageBuilder() { } protected void digest(final HttpMessage message) { if (message == null) { return; } setVersion(message.getVersion()); setHeaders(message.headerIterator()); } public ProtocolVersion getVersion() { return version; } public AbstractMessageBuilder setVersion(final ProtocolVersion version) { this.version = version; return this; } public Header[] getHeaders() { return headerGroup != null ? headerGroup.getHeaders() : null; } public Header[] getHeaders(final String name) { return headerGroup != null ? headerGroup.getHeaders(name) : null; } public AbstractMessageBuilder setHeaders(final Header... headers) { if (headerGroup == null) { headerGroup = new HeaderGroup(); } headerGroup.setHeaders(headers); return this; } public AbstractMessageBuilder setHeaders(final Iterator

it) { if (headerGroup == null) { headerGroup = new HeaderGroup(); } else { headerGroup.clear(); } while (it.hasNext()) { headerGroup.addHeader(it.next()); } return this; } public Header[] getFirstHeaders() { return headerGroup != null ? headerGroup.getHeaders() : null; } public Header getFirstHeader(final String name) { return headerGroup != null ? headerGroup.getFirstHeader(name) : null; } public Header getLastHeader(final String name) { return headerGroup != null ? headerGroup.getLastHeader(name) : null; } public AbstractMessageBuilder addHeader(final Header header) { if (headerGroup == null) { headerGroup = new HeaderGroup(); } headerGroup.addHeader(header); return this; } public AbstractMessageBuilder addHeader(final String name, final String value) { if (headerGroup == null) { headerGroup = new HeaderGroup(); } headerGroup.addHeader(new BasicHeader(name, value)); return this; } public AbstractMessageBuilder removeHeader(final Header header) { if (headerGroup == null) { headerGroup = new HeaderGroup(); } headerGroup.removeHeader(header); return this; } public AbstractMessageBuilder removeHeaders(final String name) { if (name == null || headerGroup == null) { return this; } for (final Iterator
i = headerGroup.headerIterator(); i.hasNext(); ) { final Header header = i.next(); if (name.equalsIgnoreCase(header.getName())) { i.remove(); } } return this; } public AbstractMessageBuilder setHeader(final Header header) { if (headerGroup == null) { headerGroup = new HeaderGroup(); } headerGroup.setHeader(header); return this; } public AbstractMessageBuilder setHeader(final String name, final String value) { if (headerGroup == null) { headerGroup = new HeaderGroup(); } headerGroup.setHeader(new BasicHeader(name, value)); return this; } protected abstract T build(); } httpcore5/src/main/java/org/apache/hc/core5/http/support/AbstractRequestBuilder.java0100644 0000000 0000000 00000021277 00000250600 027545 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.support; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.message.BasicNameValuePair; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.util.TextUtils; /** * Builder for {@link BasicHttpRequest} instances. * * @param The request type to build. * @since 5.1 */ public abstract class AbstractRequestBuilder extends AbstractMessageBuilder { final private String method; private String scheme; private URIAuthority authority; private String path; private Charset charset; private List parameters; private boolean absoluteRequestUri; protected AbstractRequestBuilder(final String method) { super(); this.method = method; } protected AbstractRequestBuilder(final Method method) { this(method.name()); } protected AbstractRequestBuilder(final String method, final URI uri) { super(); this.method = method; setUri(uri); } protected AbstractRequestBuilder(final Method method, final URI uri) { this(method.name(), uri); } protected AbstractRequestBuilder(final Method method, final String uri) { this(method.name(), uri != null ? URI.create(uri) : null); } protected AbstractRequestBuilder(final String method, final String uri) { this(method, uri != null ? URI.create(uri) : null); } protected void digest(final HttpRequest request) { if (request == null) { return; } setScheme(request.getScheme()); setAuthority(request.getAuthority()); setPath(request.getPath()); this.parameters = null; super.digest(request); } public String getMethod() { return method; } @Override public AbstractRequestBuilder setVersion(final ProtocolVersion version) { super.setVersion(version); return this; } public String getScheme() { return scheme; } public AbstractRequestBuilder setScheme(final String scheme) { this.scheme = scheme; return this; } public URIAuthority getAuthority() { return authority; } public AbstractRequestBuilder setAuthority(final URIAuthority authority) { this.authority = authority; return this; } public AbstractRequestBuilder setHttpHost(final HttpHost httpHost) { if (httpHost == null) { return this; } this.authority = new URIAuthority(httpHost); this.scheme = httpHost.getSchemeName(); return this; } public String getPath() { return path; } public AbstractRequestBuilder setPath(final String path) { this.path = path; return this; } public URI getUri() { final StringBuilder buf = new StringBuilder(); if (this.authority != null) { buf.append(this.scheme != null ? this.scheme : URIScheme.HTTP.id).append("://"); buf.append(this.authority.getHostName()); if (this.authority.getPort() >= 0) { buf.append(":").append(this.authority.getPort()); } } if (this.path == null) { buf.append("/"); } else { if (buf.length() > 0 && !this.path.startsWith("/")) { buf.append("/"); } buf.append(this.path); } return URI.create(buf.toString()); } public AbstractRequestBuilder setUri(final URI uri) { if (uri == null) { this.scheme = null; this.authority = null; this.path = null; } else { this.scheme = uri.getScheme(); if (uri.getHost() != null) { this.authority = new URIAuthority(uri.getRawUserInfo(), uri.getHost(), uri.getPort()); } else if (uri.getRawAuthority() != null) { try { this.authority = URIAuthority.create(uri.getRawAuthority()); } catch (final URISyntaxException ignore) { this.authority = null; } } else { this.authority = null; } final StringBuilder buf = new StringBuilder(); final String rawPath = uri.getRawPath(); if (!TextUtils.isBlank(rawPath)) { buf.append(rawPath); } else { buf.append("/"); } final String query = uri.getRawQuery(); if (query != null) { buf.append('?').append(query); } this.path = buf.toString(); } return this; } public AbstractRequestBuilder setUri(final String uri) { setUri(uri != null ? URI.create(uri) : null); return this; } @Override public AbstractRequestBuilder setHeaders(final Header... headers) { super.setHeaders(headers); return this; } @Override public AbstractRequestBuilder addHeader(final Header header) { super.addHeader(header); return this; } @Override public AbstractRequestBuilder addHeader(final String name, final String value) { super.addHeader(name, value); return this; } @Override public AbstractRequestBuilder removeHeader(final Header header) { super.removeHeader(header); return this; } @Override public AbstractRequestBuilder removeHeaders(final String name) { super.removeHeaders(name); return this; } @Override public AbstractRequestBuilder setHeader(final Header header) { super.setHeader(header); return this; } @Override public AbstractRequestBuilder setHeader(final String name, final String value) { super.setHeader(name, value); return this; } public Charset getCharset() { return charset; } public AbstractRequestBuilder setCharset(final Charset charset) { this.charset = charset; return this; } public List getParameters() { return parameters != null ? new ArrayList<>(parameters) : null; } public AbstractRequestBuilder addParameter(final NameValuePair nvp) { if (nvp == null) { return this; } if (parameters == null) { parameters = new LinkedList<>(); } parameters.add(nvp); return this; } public AbstractRequestBuilder addParameter(final String name, final String value) { return addParameter(new BasicNameValuePair(name, value)); } public AbstractRequestBuilder addParameters(final NameValuePair... nvps) { for (final NameValuePair nvp : nvps) { addParameter(nvp); } return this; } public boolean isAbsoluteRequestUri() { return absoluteRequestUri; } public AbstractRequestBuilder setAbsoluteRequestUri(final boolean absoluteRequestUri) { this.absoluteRequestUri = absoluteRequestUri; return this; } } httpcore5/src/main/java/org/apache/hc/core5/http/support/AbstractResponseBuilder.java0100644 0000000 0000000 00000006122 00000250600 027703 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.support; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.message.BasicHttpRequest; /** * Builder for {@link BasicHttpRequest} instances. * * @param The response type to build. * @since 5.1 */ public abstract class AbstractResponseBuilder extends AbstractMessageBuilder { private int status; protected AbstractResponseBuilder(final int status) { super(); this.status = status; } public int getStatus() { return status; } public void setStatus(final int status) { this.status = status; } @Override public AbstractResponseBuilder setVersion(final ProtocolVersion version) { super.setVersion(version); return this; } @Override public AbstractResponseBuilder setHeaders(final Header... headers) { super.setHeaders(headers); return this; } @Override public AbstractResponseBuilder addHeader(final Header header) { super.addHeader(header); return this; } @Override public AbstractResponseBuilder addHeader(final String name, final String value) { super.addHeader(name, value); return this; } @Override public AbstractResponseBuilder removeHeader(final Header header) { super.removeHeader(header); return this; } @Override public AbstractResponseBuilder removeHeaders(final String name) { super.removeHeaders(name); return this; } @Override public AbstractResponseBuilder setHeader(final Header header) { super.setHeader(header); return this; } @Override public AbstractResponseBuilder setHeader(final String name, final String value) { super.setHeader(name, value); return this; } @Override protected abstract T build(); } httpcore5/src/main/java/org/apache/hc/core5/http/support/BasicRequestBuilder.java0100644 0000000 0000000 00000026374 00000250600 027026 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.support; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.net.URIBuilder; import org.apache.hc.core5.util.Args; /** * Builder for {@link BasicHttpRequest} instances. * * @since 5.1 */ public class BasicRequestBuilder extends AbstractRequestBuilder { BasicRequestBuilder(final String method) { super(method); } BasicRequestBuilder(final Method method) { super(method); } BasicRequestBuilder(final String method, final URI uri) { super(method, uri); } BasicRequestBuilder(final Method method, final URI uri) { super(method, uri); } BasicRequestBuilder(final Method method, final String uri) { super(method, uri); } BasicRequestBuilder(final String method, final String uri) { super(method, uri); } public static BasicRequestBuilder create(final String method) { Args.notBlank(method, "HTTP method"); return new BasicRequestBuilder(method); } public static BasicRequestBuilder get() { return new BasicRequestBuilder(Method.GET); } public static BasicRequestBuilder get(final URI uri) { return new BasicRequestBuilder(Method.GET, uri); } public static BasicRequestBuilder get(final String uri) { return new BasicRequestBuilder(Method.GET, uri); } public static BasicRequestBuilder head() { return new BasicRequestBuilder(Method.HEAD); } public static BasicRequestBuilder head(final URI uri) { return new BasicRequestBuilder(Method.HEAD, uri); } public static BasicRequestBuilder head(final String uri) { return new BasicRequestBuilder(Method.HEAD, uri); } /** * Initializes a new {@link BasicRequestBuilder} instance for the {@code QUERY} method. * * @see Method#QUERY for more information regarding the properties of the {@code QUERY} method. * * @since 5.4 */ @Experimental public static BasicRequestBuilder query() { return new BasicRequestBuilder(Method.QUERY); } /** * Initializes a new {@link BasicRequestBuilder} instance for the {@code QUERY} method. * * @param uri the request URI. * @see Method#QUERY for more information regarding the properties of the {@code QUERY} method. * * @since 5.4 */ @Experimental public static BasicRequestBuilder query(final URI uri) { return new BasicRequestBuilder(Method.QUERY, uri); } /** * Initializes a new {@link BasicRequestBuilder} instance for the {@code QUERY} method. * * @param uri the request URI. * @see Method#QUERY for more information regarding the properties of the {@code QUERY} method. * * @since 5.4 */ @Experimental public static BasicRequestBuilder query(final String uri) { return new BasicRequestBuilder(Method.QUERY, uri); } public static BasicRequestBuilder patch() { return new BasicRequestBuilder(Method.PATCH); } public static BasicRequestBuilder patch(final URI uri) { return new BasicRequestBuilder(Method.PATCH, uri); } public static BasicRequestBuilder patch(final String uri) { return new BasicRequestBuilder(Method.PATCH, uri); } public static BasicRequestBuilder post() { return new BasicRequestBuilder(Method.POST); } public static BasicRequestBuilder post(final URI uri) { return new BasicRequestBuilder(Method.POST, uri); } public static BasicRequestBuilder post(final String uri) { return new BasicRequestBuilder(Method.POST, uri); } public static BasicRequestBuilder put() { return new BasicRequestBuilder(Method.PUT); } public static BasicRequestBuilder put(final URI uri) { return new BasicRequestBuilder(Method.PUT, uri); } public static BasicRequestBuilder put(final String uri) { return new BasicRequestBuilder(Method.PUT, uri); } public static BasicRequestBuilder delete() { return new BasicRequestBuilder(Method.DELETE); } public static BasicRequestBuilder delete(final URI uri) { return new BasicRequestBuilder(Method.DELETE, uri); } public static BasicRequestBuilder delete(final String uri) { return new BasicRequestBuilder(Method.DELETE, uri); } public static BasicRequestBuilder trace() { return new BasicRequestBuilder(Method.TRACE); } public static BasicRequestBuilder trace(final URI uri) { return new BasicRequestBuilder(Method.TRACE, uri); } public static BasicRequestBuilder trace(final String uri) { return new BasicRequestBuilder(Method.TRACE, uri); } public static BasicRequestBuilder options() { return new BasicRequestBuilder(Method.OPTIONS); } public static BasicRequestBuilder options(final URI uri) { return new BasicRequestBuilder(Method.OPTIONS, uri); } public static BasicRequestBuilder options(final String uri) { return new BasicRequestBuilder(Method.OPTIONS, uri); } public static BasicRequestBuilder copy(final HttpRequest request) { Args.notNull(request, "HTTP request"); final BasicRequestBuilder builder = new BasicRequestBuilder(request.getMethod()); builder.digest(request); return builder; } @Override public BasicRequestBuilder setVersion(final ProtocolVersion version) { super.setVersion(version); return this; } @Override public BasicRequestBuilder setUri(final URI uri) { super.setUri(uri); return this; } @Override public BasicRequestBuilder setUri(final String uri) { super.setUri(uri); return this; } @Override public BasicRequestBuilder setScheme(final String scheme) { super.setScheme(scheme); return this; } @Override public BasicRequestBuilder setAuthority(final URIAuthority authority) { super.setAuthority(authority); return this; } /** * @since 5.1 */ @Override public BasicRequestBuilder setHttpHost(final HttpHost httpHost) { super.setHttpHost(httpHost); return this; } @Override public BasicRequestBuilder setPath(final String path) { super.setPath(path); return this; } @Override public BasicRequestBuilder setHeaders(final Header... headers) { super.setHeaders(headers); return this; } @Override public BasicRequestBuilder addHeader(final Header header) { super.addHeader(header); return this; } @Override public BasicRequestBuilder addHeader(final String name, final String value) { super.addHeader(name, value); return this; } @Override public BasicRequestBuilder removeHeader(final Header header) { super.removeHeader(header); return this; } @Override public BasicRequestBuilder removeHeaders(final String name) { super.removeHeaders(name); return this; } @Override public BasicRequestBuilder setHeader(final Header header) { super.setHeader(header); return this; } @Override public BasicRequestBuilder setHeader(final String name, final String value) { super.setHeader(name, value); return this; } @Override public BasicRequestBuilder setCharset(final Charset charset) { super.setCharset(charset); return this; } @Override public BasicRequestBuilder addParameter(final NameValuePair nvp) { super.addParameter(nvp); return this; } @Override public BasicRequestBuilder addParameter(final String name, final String value) { super.addParameter(name, value); return this; } @Override public BasicRequestBuilder addParameters(final NameValuePair... nvps) { super.addParameters(nvps); return this; } @Override public BasicRequestBuilder setAbsoluteRequestUri(final boolean absoluteRequestUri) { super.setAbsoluteRequestUri(absoluteRequestUri); return this; } @Override public BasicHttpRequest build() { String path = getPath(); final List parameters = getParameters(); if (parameters != null && !parameters.isEmpty()) { try { final URI uri = new URIBuilder(path) .setCharset(getCharset()) .addParameters(parameters) .build(); path = uri.toASCIIString(); } catch (final URISyntaxException ex) { // should never happen } } final BasicHttpRequest result = new BasicHttpRequest(getMethod(), getScheme(), getAuthority(), path); result.setVersion(getVersion()); result.setHeaders(getHeaders()); result.setAbsoluteRequestUri(isAbsoluteRequestUri()); return result; } @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("BasicRequestBuilder [method="); builder.append(getMethod()); builder.append(", scheme="); builder.append(getScheme()); builder.append(", authority="); builder.append(getAuthority()); builder.append(", path="); builder.append(getPath()); builder.append(", parameters="); builder.append(getParameters()); builder.append(", headerGroup="); builder.append(Arrays.toString(getHeaders())); builder.append("]"); return builder.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/support/BasicResponseBuilder.java0100644 0000000 0000000 00000007650 00000250600 027170 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.support; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.util.Args; import java.util.Arrays; /** * Builder for {@link BasicHttpResponse} instances. * * @since 5.1 */ public class BasicResponseBuilder extends AbstractResponseBuilder { protected BasicResponseBuilder(final int status) { super(status); } public static BasicResponseBuilder create(final int status) { Args.checkRange(status, 100, 599, "HTTP status code"); return new BasicResponseBuilder(status); } public static BasicResponseBuilder copy(final HttpResponse response) { Args.notNull(response, "HTTP response"); final BasicResponseBuilder builder = new BasicResponseBuilder(response.getCode()); builder.digest(response); return builder; } @Override public BasicResponseBuilder setVersion(final ProtocolVersion version) { super.setVersion(version); return this; } @Override public BasicResponseBuilder setHeaders(final Header... headers) { super.setHeaders(headers); return this; } @Override public BasicResponseBuilder addHeader(final Header header) { super.addHeader(header); return this; } @Override public BasicResponseBuilder addHeader(final String name, final String value) { super.addHeader(name, value); return this; } @Override public BasicResponseBuilder removeHeader(final Header header) { super.removeHeader(header); return this; } @Override public BasicResponseBuilder removeHeaders(final String name) { super.removeHeaders(name); return this; } @Override public BasicResponseBuilder setHeader(final Header header) { super.setHeader(header); return this; } @Override public BasicResponseBuilder setHeader(final String name, final String value) { super.setHeader(name, value); return this; } @Override public BasicHttpResponse build() { final BasicHttpResponse result = new BasicHttpResponse(getStatus()); result.setVersion(getVersion()); result.setHeaders(getHeaders()); return result; } @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("BasicResponseBuilder [status="); builder.append(getStatus()); builder.append(", headerGroup="); builder.append(Arrays.toString(getHeaders())); builder.append("]"); return builder.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/http/support/ExpectSupport.java0100644 0000000 0000000 00000005473 00000250600 025747 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.support; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.util.TextUtils; /** * Server side Expect header handling support. * * @since 5.3 */ @Internal public class ExpectSupport { public static Expectation parse( final HttpRequest request, final EntityDetails entityDetails) throws ProtocolException { // Ignore Expect header in messages older than HTTP/1.1 if (request.getVersion() != null && request.getVersion().lessEquals(HttpVersion.HTTP_1_0)) { return null; } final AtomicReference expectationRef = new AtomicReference<>(); MessageSupport.parseTokens(request, HttpHeaders.EXPECT, t -> { if (t.equalsIgnoreCase(HeaderElements.CONTINUE)) { expectationRef.compareAndSet(null, Expectation.CONTINUE); } else if (!TextUtils.isBlank(t)) { expectationRef.set(Expectation.UNKNOWN); } }); final Expectation expectation = expectationRef.get(); if (expectation == Expectation.CONTINUE && entityDetails == null) { throw new ProtocolException("Expect-Continue request without an enclosed entity"); } return expectation; } } httpcore5/src/main/java/org/apache/hc/core5/http/support/Expectation.java0100644 0000000 0000000 00000002643 00000250600 025401 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.support; import org.apache.hc.core5.annotation.Internal; /** * Message expectations. Presently only {@literal 100-continue} is recognized. * * @since 5.3 */ @Internal public enum Expectation { CONTINUE, UNKNOWN } httpcore5/src/main/java/org/apache/hc/core5/io/0040755 0000000 0000000 00000000000 00000250600 020165 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/io/CloseMode.java0100644 0000000 0000000 00000002450 00000250600 022700 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.io; /** * Close operation mode. * * @since 5.0 */ public enum CloseMode { IMMEDIATE, GRACEFUL } httpcore5/src/main/java/org/apache/hc/core5/io/Closer.java0100644 0000000 0000000 00000004476 00000250600 022267 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.io; import java.io.Closeable; import java.io.IOException; /** * Closes resources. * * @since 5.0 */ public final class Closer { /** * Closes the given Closeable in a null-safe manner. * * @param closeable what to close. * @throws IOException */ public static void close(final Closeable closeable) throws IOException { if (closeable != null) { closeable.close(); } } /** * Closes the given Closeable in a null-safe manner. * * @param closeable what to close. * @param closeMode How to close the given resource. */ public static void close(final ModalCloseable closeable, final CloseMode closeMode) { if (closeable != null) { closeable.close(closeMode); } } /** * Closes the given Closeable quietly in a null-safe manner even in the event of an exception. * * @param closeable what to close. */ public static void closeQuietly(final Closeable closeable) { try { close(closeable); } catch (final IOException e) { // Quietly ignore } } } httpcore5/src/main/java/org/apache/hc/core5/io/IOCallback.java0100644 0000000 0000000 00000002634 00000250600 022756 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.io; import java.io.IOException; /** * Abstract I/O callback. * * @param the type of the input to the operation. * @since 5.0 */ public interface IOCallback { void execute(T object) throws IOException; } httpcore5/src/main/java/org/apache/hc/core5/io/IOFunction.java0100644 0000000 0000000 00000003363 00000250600 023047 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.io; import java.io.IOException; /** * Minimal equivalent of {@link java.util.function.Function} whose * {@link #apply(Object)} method is allowed to throw {@link IOException}. * * @param input type * @param result type * @since 5.4 */ @FunctionalInterface public interface IOFunction { /** * Applies the transformation. * * @param value source value (never {@code null}) * @return transformed value * @throws IOException if the transformation cannot be performed */ R apply(T value) throws IOException; }httpcore5/src/main/java/org/apache/hc/core5/io/ModalCloseable.java0100644 0000000 0000000 00000003256 00000250600 023701 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.io; import java.io.Closeable; /** * Process or endpoint that can be closed either immediately or gracefully. * * @since 5.0 */ public interface ModalCloseable extends Closeable { /** * Closes this process or endpoint and releases any system resources associated * with it. If the endpoint or the process is already closed then invoking this * method has no effect. * * @param closeMode How to close the receiver. */ void close(CloseMode closeMode); } httpcore5/src/main/java/org/apache/hc/core5/io/SocketSupport.java0100644 0000000 0000000 00000006151 00000250600 023655 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.io; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.SocketOption; import org.apache.hc.core5.annotation.Internal; /** * @since 5.3 * * @deprecated Do not use. */ @Internal @Deprecated public class SocketSupport { public static final String TCP_KEEPIDLE = "TCP_KEEPIDLE"; public static final String TCP_KEEPINTERVAL = "TCP_KEEPINTERVAL"; public static final String TCP_KEEPCOUNT = "TCP_KEEPCOUNT"; @SuppressWarnings("unchecked") public static SocketOption getExtendedSocketOptionOrNull(final String fieldName) { try { final Class extendedSocketOptionsClass = Class.forName("jdk.net.ExtendedSocketOptions"); final Field field = extendedSocketOptionsClass.getField(fieldName); return (SocketOption) field.get(null); } catch (final Exception ignore) { return null; } } /** * Object can be ServerSocket or Socket. * * @param ServerSocket or Socket. * @throws IOException in case of an I/O error. */ public static void setOption(final T object, final String fieldName, final T value) throws IOException { try { final Class serverSocketClass = object.getClass(); final Method setOptionMethod = serverSocketClass.getMethod("setOption", SocketOption.class, Object.class); final SocketOption socketOption = getExtendedSocketOptionOrNull(fieldName); if (socketOption == null) { throw new UnsupportedOperationException("Extended socket option not supported: " + fieldName); } setOptionMethod.invoke(object, socketOption, value); } catch (final UnsupportedOperationException e) { throw e; } catch (final Exception ex) { throw new IOException("Failure setting extended socket option", ex); } } } httpcore5/src/main/java/org/apache/hc/core5/io/SocketTimeoutExceptionFactory.java0100644 0000000 0000000 00000003517 00000250600 027041 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.io; import java.net.SocketTimeoutException; import java.util.Objects; import org.apache.hc.core5.util.Timeout; /** * Creates {@link SocketTimeoutException} instances. * * @since 5.0 */ public final class SocketTimeoutExceptionFactory { /** * Creates a new {@link SocketTimeoutException} with a message for the given timeout. * * @param timeout * the timeout value. * @return a new {@link SocketTimeoutException} with a message for the given timeout. */ static public SocketTimeoutException create(final Timeout timeout) { return new SocketTimeoutException(Objects.toString(timeout)); } } httpcore5/src/main/java/org/apache/hc/core5/io/package-info.java0100644 0000000 0000000 00000002363 00000250600 023355 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Core I/O component APIs and utilities. */ package org.apache.hc.core5.io; httpcore5/src/main/java/org/apache/hc/core5/net/0040755 0000000 0000000 00000000000 00000250600 020344 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/net/Host.java0100644 0000000 0000000 00000016137 00000250600 022131 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.net; import java.io.Serializable; import java.net.IDN; import java.net.URISyntaxException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.LangUtils; import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.util.Tokenizer; /** * Component that holds all details needed to describe a network connection * to a host. This includes remote host name and port. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public final class Host implements NamedEndpoint, Serializable { private static final long serialVersionUID = 1L; private final String name; private final String lcName; private final int port; static boolean isPunyCode(final CharSequence s) { if (s == null || s.length() < 4) { return false; } return (s.charAt(0) == 'x' || s.charAt(0) == 'X') && (s.charAt(1) == 'n' || s.charAt(1) == 'N') && s.charAt(2) == '-' && s.charAt(3) == '-'; } /** * Constructs a new instance. * * @param name The host name, not null. * @param port The port value, between 0 and 65535, inclusive. {@code -1} indicates the scheme default port. * @throws NullPointerException if the {@code name} is {@code null}. * @throws IllegalArgumentException If the port parameter is outside the specified range of valid port values, which is between 0 and 65535, inclusive. * {@code -1} indicates the scheme default port. */ public Host(final String name, final int port) { super(); Args.notNull(name, "Host name"); Ports.checkWithDefault(port); this.name = isPunyCode(name) ? IDN.toUnicode(name) : name; this.port = port; this.lcName = TextUtils.toLowerCase(this.name); } static Host parse(final CharSequence s, final Tokenizer.Cursor cursor) throws URISyntaxException { final Tokenizer tokenizer = Tokenizer.INSTANCE; final String hostName; final boolean ipv6Brackets = !cursor.atEnd() && s.charAt(cursor.getPos()) == '['; if (ipv6Brackets) { cursor.updatePos(cursor.getPos() + 1); hostName = tokenizer.parseContent(s, cursor, URISupport.IPV6_HOST_DELIMITERS); if (cursor.atEnd() || !(s.charAt(cursor.getPos()) == ']')) { throw URISupport.createException(s, cursor, "Expected an IPv6 closing bracket ']'"); } cursor.updatePos(cursor.getPos() + 1); if (!InetAddressUtils.isIPv6(hostName)) { throw URISupport.createException(s, cursor, "Expected an IPv6 address"); } } else { hostName = tokenizer.parseContent(s, cursor, URISupport.PORT_DELIMITERS); } String portText = null; if (!cursor.atEnd() && s.charAt(cursor.getPos()) == ':') { cursor.updatePos(cursor.getPos() + 1); portText = tokenizer.parseContent(s, cursor, URISupport.DELIMITERS); } final int port; if (!TextUtils.isBlank(portText)) { if (!ipv6Brackets && portText.contains(":")) { throw URISupport.createException(s, cursor, "Expected IPv6 address to be enclosed in brackets"); } try { port = Integer.parseInt(portText); } catch (final NumberFormatException ex) { throw URISupport.createException(s, cursor, "Port is invalid"); } } else { port = -1; } return new Host(hostName, port); } static Host parse(final CharSequence s) throws URISyntaxException { final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); return parse(s, cursor); } static void format(final StringBuilder buf, final NamedEndpoint endpoint) { final String hostName = endpoint.getHostName(); if (InetAddressUtils.isIPv6(hostName)) { buf.append('[').append(hostName).append(']'); } else { if (TextUtils.isAllASCII(hostName)) { buf.append(hostName); } else { buf.append(IDN.toASCII(hostName)); } } if (endpoint.getPort() != -1) { buf.append(":"); buf.append(endpoint.getPort()); } } static void format(final StringBuilder buf, final Host host) { format(buf, (NamedEndpoint) host); } static String format(final Host host) { final StringBuilder buf = new StringBuilder(); format(buf, host); return buf.toString(); } public static Host create(final String s) throws URISyntaxException { Args.notEmpty(s, "HTTP Host"); final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); final Host host = parse(s, cursor); if (TextUtils.isBlank(host.getHostName())) { throw URISupport.createException(s, cursor, "Hostname is invalid"); } if (!cursor.atEnd()) { throw URISupport.createException(s, cursor, "Unexpected content"); } return host; } @Override public String getHostName() { return name; } @Override public int getPort() { return port; } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (o instanceof Host) { final Host that = (Host) o; return this.lcName.equals(that.lcName) && this.port == that.port; } return false; } @Override public int hashCode() { int hash = LangUtils.HASH_SEED; hash = LangUtils.hashCode(hash, this.lcName); hash = LangUtils.hashCode(hash, this.port); return hash; } @Override public String toString() { return format(this); } } httpcore5/src/main/java/org/apache/hc/core5/net/InetAddressUtils.java0100644 0000000 0000000 00000027046 00000250600 024443 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.net; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.UnknownHostException; import java.util.regex.Pattern; import org.apache.hc.core5.util.Args; /** * A collection of utilities relating to InetAddresses. * * @since 4.0 */ public class InetAddressUtils { /** * Represents the ipv4 * * @since 5.1 */ public static final byte IPV4 = 1; /** * Represents the ipv6. * * @since 5.1 */ public static final byte IPV6 = 4; private InetAddressUtils() { } private static final String IPV4_BASIC_PATTERN_STRING = "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}" + // First 3 fields, 0-255, followed by . "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])"; // final field, 0-255 private static final Pattern IPV4_PATTERN = Pattern.compile("^" + IPV4_BASIC_PATTERN_STRING + "$"); // Accept IPv4-mapped IPv6, allowing redundant leading zeros in the IPv4 part. // Examples: ::ffff:1.2.3.4, ::FFFF:001.002.003.004 // Still enforces each octet <= 255 and max 3 digits per octet. private static final String IPV4_MAPPED_OCTET = "(25[0-5]|2[0-4]\\d|1\\d\\d|0?\\d?\\d)"; private static final Pattern IPV4_MAPPED_IPV6_PATTERN = Pattern.compile("^::[fF]{4}:((" + IPV4_MAPPED_OCTET + ")\\.){3}" + IPV4_MAPPED_OCTET + "$"); private static final Pattern IPV6_STD_PATTERN = Pattern.compile( "^[0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4}){7}$"); private static final Pattern IPV6_HEX_COMPRESSED_PATTERN = Pattern.compile( "^(([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,5})?)" + // 0-6 hex fields "::" + "(([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,5})?)$"); // 0-6 hex fields /** * Regular expression pattern to match the scope ID in an IPv6 scoped address. * The scope ID should be a non-empty string consisting of only alphanumeric characters or "-". */ private static final Pattern SCOPE_ID_PATTERN = Pattern.compile("^[a-zA-Z0-9\\-]+$"); /** * Delimiter used to separate an IPv6 address from its scope ID. */ private static final char SCOPE_ID_DELIMITER = '%'; /* * The above pattern is not totally rigorous as it allows for more than 7 hex fields in total */ private static final char COLON_CHAR = ':'; // Must not have more than 7 colons (i.e. 8 fields) private static final int MAX_COLON_COUNT = 7; /** * Tests whether the parameter is a valid IPv4 address * * @param input the address character sequence to check for validity * @return true if the input parameter is a valid IPv4 address * @deprecated Use {@link #isIPv4(CharSequence)} */ @Deprecated public static boolean isIPv4Address(final String input) { return isIPv4(input); } /** * Tests whether the parameter is a valid IPv4 address. * * @param input the address character sequence to check for validity * @return true if the input parameter is a valid IPv4 address * @since 5.3 */ public static boolean isIPv4(final CharSequence input) { return IPV4_PATTERN.matcher(input).matches(); } /** * Tests if an IPv6 address is an IPv4-mapped IPv6 address. * * @param input the IPv6 address to be checked * @return true if the IPv6 address is an IPv4-mapped IPv6 address, false otherwise. * @deprecated Use {@link #isIPv4MappedIPv6(CharSequence)} */ @Deprecated public static boolean isIPv4MappedIPv64Address(final String input) { return isIPv4MappedIPv6(input); } /** * Tests if an IPv6 address is an IPv4-mapped IPv6 address. * * @param input the IPv6 address to be checked * @return true if the IPv6 address is an IPv4-mapped IPv6 address, false otherwise. * @since 5.3 */ public static boolean isIPv4MappedIPv6(final CharSequence input) { return IPV4_MAPPED_IPV6_PATTERN.matcher(input).matches(); } static boolean hasValidIPv6ColonCount(final CharSequence input) { int colonCount = 0; for (int i = 0; i < input.length(); i++) { if (input.charAt(i) == COLON_CHAR) { colonCount++; } } // IPv6 address must have at least 2 colons and not more than 7 (i.e. 8 fields) return colonCount >= 2 && colonCount <= MAX_COLON_COUNT; } /** * Tests whether the parameter is a valid standard (non-compressed) IPv6 address * * @param input the address character sequence to check for validity * @return true if the input parameter is a valid standard (non-compressed) IPv6 address * @deprecated Use {@link #isIPv6Std(CharSequence)} */ @Deprecated public static boolean isIPv6StdAddress(final String input) { return isIPv6Std(input); } /** * Tests whether the parameter is a valid standard (non-compressed) IPv6 address * * @param input the address character sequence to check for validity * @return true if the input parameter is a valid standard (non-compressed) IPv6 address * @since 5.3 */ public static boolean isIPv6Std(final CharSequence input) { return hasValidIPv6ColonCount(input) && IPV6_STD_PATTERN.matcher(input).matches(); } /** * Tests whether the parameter is a valid compressed IPv6 address * * @param input the address character sequence to check for validity * @return true if the input parameter is a valid compressed IPv6 address * @deprecated Use {@link #isIPv6HexCompressed(CharSequence)} */ @Deprecated public static boolean isIPv6HexCompressedAddress(final String input) { return isIPv6HexCompressed(input); } /** * Tests whether the parameter is a valid compressed IPv6 address * * @param input the address character sequence to check for validity * @return true if the input parameter is a valid compressed IPv6 address * @since 5.3 */ public static boolean isIPv6HexCompressed(final CharSequence input) { return hasValidIPv6ColonCount(input) && IPV6_HEX_COMPRESSED_PATTERN.matcher(input).matches(); } /** * Tests whether the parameter is a valid IPv6 address (including compressed). * * @param input the address character sequence to check for validity * @return true if the input parameter is a valid standard or compressed IPv6 address * @deprecated Use {@link #isIPv6(CharSequence)} */ @Deprecated public static boolean isIPv6Address(final String input) { return isIPv6(input); } /** * Tests whether the parameter is a valid IPv6 address (including compressed). * * @param input the address character sequence to check for validity * @return true if the input parameter is a valid standard or compressed IPv6 address * @since 5.3 */ public static boolean isIPv6(final CharSequence input) { int index = -1; for (int i = 0; i < input.length(); i++) { if (input.charAt(i) == SCOPE_ID_DELIMITER) { index = i; break; } } if (index == -1) { return isIPv6Std(input) || isIPv6HexCompressed(input); } final CharSequence address = input.subSequence(0, index); if (isIPv6Std(address) || isIPv6HexCompressed(address)) { // Check if the scope ID is valid final CharSequence scopeId = input.subSequence(index + 1, input.length()); // Scope ID should be a non-empty character sequence consisting of only alphanumeric characters or "-" return scopeId.length() != 0 && SCOPE_ID_PATTERN.matcher(scopeId).matches(); } return false; } /** * Tests whether the parameter is a valid URL formatted bracketed IPv6 address (including compressed). * This matches only bracketed values e.g. {@code [::1]}. * * @param input the address character sequence to check for validity * @return true if the input parameter is a valid URL-formatted bracketed IPv6 address * @deprecated Use {@link #isIPv6URLBracketed(CharSequence)} */ @Deprecated public static boolean isIPv6URLBracketedAddress(final String input) { return isIPv6URLBracketed(input); } /** * Tests whether the parameter is a valid URL formatted bracketed IPv6 address (including compressed). * This matches only bracketed values e.g. {@code [::1]}. * * @param input the address character sequence to check for validity * @return true if the input parameter is a valid URL-formatted bracketed IPv6 address * @since 5.3 */ public static boolean isIPv6URLBracketed(final CharSequence input) { if (input.length() == 0) { return false; } return input.charAt(0) == '[' && input.charAt(input.length() - 1) == ']' && isIPv6(input.subSequence(1, input.length() - 1)); } /** * Formats {@link SocketAddress} as text. * * @param buffer The target buffer to append. * @param socketAddress The SocketAddress to append to {@code buffer}. * @since 5.0 */ public static void formatAddress( final StringBuilder buffer, final SocketAddress socketAddress) { Args.notNull(buffer, "buffer"); if (socketAddress instanceof InetSocketAddress) { final InetSocketAddress socketaddr = (InetSocketAddress) socketAddress; final InetAddress inetaddr = socketaddr.getAddress(); if (inetaddr != null) { buffer.append(inetaddr.getHostAddress()).append(':').append(socketaddr.getPort()); } else { buffer.append(socketAddress); } } else { buffer.append(socketAddress); } } /** * Gets the canonical name of the local host, a fully qualified domain name. *

* This can be {@code "localhost"} or a fully qualified domain name like {@code "host.docker.internal"}. *

* * @return the canonical name of the local host. * @since 5.0 */ public static String getCanonicalLocalHostName() { try { return InetAddress.getLocalHost().getCanonicalHostName(); } catch (final UnknownHostException ex) { return "localhost"; } } } httpcore5/src/main/java/org/apache/hc/core5/net/NamedEndpoint.java0100644 0000000 0000000 00000003122 00000250600 023727 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.net; /** * Endpoint identified by name (usually a fully qualified domain name) and port. * * @since 5.0 */ public interface NamedEndpoint { /** * Returns name (IP or DNS name). * * @return the host name (IP or DNS name) */ String getHostName(); /** * Returns the port. * * @return the host port, or {@code -1} if not set */ int getPort(); } httpcore5/src/main/java/org/apache/hc/core5/net/PercentCodec.java0100644 0000000 0000000 00000020264 00000250600 023546 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.net; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.BitSet; /** * Percent-encoding. * * @since 5.1 */ public class PercentCodec { static final BitSet GEN_DELIMS = new BitSet(256); static final BitSet SUB_DELIMS = new BitSet(256); static final BitSet UNRESERVED = new BitSet(256); static final BitSet URIC = new BitSet(256); static { GEN_DELIMS.set(':'); GEN_DELIMS.set('/'); GEN_DELIMS.set('?'); GEN_DELIMS.set('#'); GEN_DELIMS.set('['); GEN_DELIMS.set(']'); GEN_DELIMS.set('@'); SUB_DELIMS.set('!'); SUB_DELIMS.set('$'); SUB_DELIMS.set('&'); SUB_DELIMS.set('\''); SUB_DELIMS.set('('); SUB_DELIMS.set(')'); SUB_DELIMS.set('*'); SUB_DELIMS.set('+'); SUB_DELIMS.set(','); SUB_DELIMS.set(';'); SUB_DELIMS.set('='); for (int i = 'a'; i <= 'z'; i++) { UNRESERVED.set(i); } for (int i = 'A'; i <= 'Z'; i++) { UNRESERVED.set(i); } // numeric characters for (int i = '0'; i <= '9'; i++) { UNRESERVED.set(i); } UNRESERVED.set('-'); UNRESERVED.set('.'); UNRESERVED.set('_'); UNRESERVED.set('~'); URIC.or(SUB_DELIMS); URIC.or(UNRESERVED); } static final BitSet RFC5987_UNRESERVED = new BitSet(256); static { // Alphanumeric characters for (int i = 'a'; i <= 'z'; i++) { RFC5987_UNRESERVED.set(i); } for (int i = 'A'; i <= 'Z'; i++) { RFC5987_UNRESERVED.set(i); } for (int i = '0'; i <= '9'; i++) { RFC5987_UNRESERVED.set(i); } // Additional characters as per RFC 5987 attr-char RFC5987_UNRESERVED.set('!'); RFC5987_UNRESERVED.set('#'); RFC5987_UNRESERVED.set('$'); RFC5987_UNRESERVED.set('&'); RFC5987_UNRESERVED.set('+'); RFC5987_UNRESERVED.set('-'); RFC5987_UNRESERVED.set('.'); RFC5987_UNRESERVED.set('^'); RFC5987_UNRESERVED.set('_'); RFC5987_UNRESERVED.set('`'); RFC5987_UNRESERVED.set('|'); RFC5987_UNRESERVED.set('~'); } static final BitSet PCHAR = new BitSet(256); static final BitSet USERINFO = new BitSet(256); static final BitSet REG_NAME = new BitSet(256); static final BitSet PATH_SEGMENT = new BitSet(256); static final BitSet QUERY = new BitSet(256); static final BitSet FRAGMENT = new BitSet(256); static { PCHAR.or(UNRESERVED); PCHAR.or(SUB_DELIMS); PCHAR.set(':'); PCHAR.set('@'); USERINFO.or(UNRESERVED); USERINFO.or(SUB_DELIMS); USERINFO.set(':'); REG_NAME.or(UNRESERVED); REG_NAME.or(SUB_DELIMS); PATH_SEGMENT.or(PCHAR); QUERY.or(PCHAR); QUERY.set('/'); QUERY.set('?'); FRAGMENT.or(PCHAR); FRAGMENT.set('/'); FRAGMENT.set('?'); } private static final int RADIX = 16; static void encode(final StringBuilder buf, final CharSequence content, final Charset charset, final BitSet safechars, final boolean blankAsPlus) { if (content == null) { return; } final CharBuffer cb = CharBuffer.wrap(content); final ByteBuffer bb = (charset != null ? charset : StandardCharsets.UTF_8).encode(cb); while (bb.hasRemaining()) { final int b = bb.get() & 0xff; if (safechars.get(b)) { buf.append((char) b); } else if (blankAsPlus && b == ' ') { buf.append("+"); } else { buf.append("%"); final char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, RADIX)); final char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, RADIX)); buf.append(hex1); buf.append(hex2); } } } static void encode(final StringBuilder buf, final CharSequence content, final Charset charset, final boolean blankAsPlus) { encode(buf, content, charset, UNRESERVED, blankAsPlus); } public static void encode(final StringBuilder buf, final CharSequence content, final Charset charset) { encode(buf, content, charset, UNRESERVED, false); } public static String encode(final CharSequence content, final Charset charset) { if (content == null) { return null; } final StringBuilder buf = new StringBuilder(); encode(buf, content, charset, UNRESERVED, false); return buf.toString(); } static String decode(final CharSequence content, final Charset charset, final boolean plusAsBlank) { if (content == null) { return null; } final ByteBuffer bb = ByteBuffer.allocate(content.length()); final CharBuffer cb = CharBuffer.wrap(content); while (cb.hasRemaining()) { final char c = cb.get(); if (c == '%' && cb.remaining() >= 2) { final char uc = cb.get(); final char lc = cb.get(); final int u = Character.digit(uc, RADIX); final int l = Character.digit(lc, RADIX); if (u != -1 && l != -1) { bb.put((byte) ((u << 4) + l)); } else { bb.put((byte) '%'); bb.put((byte) uc); bb.put((byte) lc); } } else if (plusAsBlank && c == '+') { bb.put((byte) ' '); } else { bb.put((byte) c); } } bb.flip(); return (charset != null ? charset : StandardCharsets.UTF_8).decode(bb).toString(); } public static String decode(final CharSequence content, final Charset charset) { return decode(content, charset, false); } public static final PercentCodec RFC3986 = new PercentCodec(UNRESERVED); public static final PercentCodec RFC5987 = new PercentCodec(RFC5987_UNRESERVED); private final BitSet unreserved; private PercentCodec(final BitSet unreserved) { this.unreserved = unreserved; } public PercentCodec() { this.unreserved = UNRESERVED; } /** * @since 5.3 */ public void encode(final StringBuilder buf, final CharSequence content) { encode(buf, content, StandardCharsets.UTF_8, unreserved, false); } /** * @since 5.3 */ public String encode(final CharSequence content) { if (content == null) { return null; } final StringBuilder buf = new StringBuilder(); encode(buf, content, StandardCharsets.UTF_8, unreserved, false); return buf.toString(); } /** * @since 5.3 */ public String decode(final CharSequence content) { return decode(content, StandardCharsets.UTF_8, false); } } httpcore5/src/main/java/org/apache/hc/core5/net/Ports.java0100644 0000000 0000000 00000005427 00000250600 022323 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.net; import org.apache.hc.core5.util.Args; /** * Port helper methods. * * @since 5.0 */ public class Ports { /** * The scheme default port. */ public final static int SCHEME_DEFAULT = -1; /** * The minimum port value per https://tools.ietf.org/html/rfc6335. */ public final static int MIN_VALUE = 0; /** * The maximum port value per https://tools.ietf.org/html/rfc6335. */ public final static int MAX_VALUE = 65535; /** * Checks a port number where {@code -1} indicates the scheme default port. * * @param port * The port to check where {@code -1} indicates the scheme default port. * @return the port * * @throws IllegalArgumentException * If the port parameter is outside the specified range of valid port values, which is between 0 and * 65535, inclusive. {@code -1} indicates the scheme default port. */ public static int checkWithDefault(final int port) { return Args.checkRange(port, SCHEME_DEFAULT, MAX_VALUE, "Port number(Use -1 to specify the scheme default port)"); } /** * Checks a port number. * * @param port * The port to check. * @return the port * * @throws IllegalArgumentException * If the port parameter is outside the specified range of valid port values, which is between 0 and * 65535, inclusive. */ public static int check(final int port) { return Args.checkRange(port, MIN_VALUE, MAX_VALUE, "Port number"); } } httpcore5/src/main/java/org/apache/hc/core5/net/URIAuthority.java0100644 0000000 0000000 00000021272 00000250600 023560 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.net; import java.io.Serializable; import java.net.URI; import java.net.URISyntaxException; import java.util.Objects; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.LangUtils; import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.util.Tokenizer; /** * Represents authority component of request {@link URI}. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public final class URIAuthority implements NamedEndpoint, Serializable { private static final long serialVersionUID = 1L; private final String userInfo; private final Host host; static URIAuthority parse(final CharSequence s, final Tokenizer.Cursor cursor) throws URISyntaxException { final Tokenizer tokenizer = Tokenizer.INSTANCE; String userInfo = null; final int initPos = cursor.getPos(); final String token = tokenizer.parseContent(s, cursor, URISupport.HOST_DELIMITERS); if (!cursor.atEnd() && s.charAt(cursor.getPos()) == '@') { cursor.updatePos(cursor.getPos() + 1); if (!TextUtils.isBlank(token)) { userInfo = token; } } else { //Rewind cursor.updatePos(initPos); } final Host host = Host.parse(s, cursor); return new URIAuthority(userInfo, host); } static URIAuthority parse(final CharSequence s) throws URISyntaxException { final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); return parse(s, cursor); } static void format(final StringBuilder buf, final URIAuthority uriAuthority) { if (uriAuthority.getUserInfo() != null) { buf.append(uriAuthority.getUserInfo()); buf.append("@"); } Host.format(buf, uriAuthority); } static String format(final URIAuthority uriAuthority) { final StringBuilder buf = new StringBuilder(); format(buf, uriAuthority); return buf.toString(); } /** * Constructs a new instance. * * @param userInfo The user info, may be null. * @param hostName The host name, not null. * @param port The port value, between 0 and 65535, inclusive. {@code -1} indicates the scheme default port. * @throws NullPointerException If the {@code name} is {@code null}. * @throws IllegalArgumentException If the port is outside the specified range of valid port values, which is between 0 and 65535, inclusive. * {@code -1} indicates the scheme default port. */ public URIAuthority(final String userInfo, final String hostName, final int port) { super(); this.userInfo = userInfo; this.host = new Host(hostName, port); } /** * Constructs a new instance. * * @param hostName The host name, not null. * @param port The port value, between 0 and 65535, inclusive. {@code -1} indicates the scheme default port. * @throws NullPointerException If the {@code name} is {@code null}. * @throws IllegalArgumentException If the port is outside the specified range of valid port values, which is between 0 and 65535, inclusive. * {@code -1} indicates the scheme default port. */ public URIAuthority(final String hostName, final int port) { this(null, hostName, port); } /** * Constructs a new instance. * * @param userInfo The user info, may be null. * @param host The host, never null. * @throws NullPointerException if the {@code host} is {@code null}. * @since 5.2 */ public URIAuthority(final String userInfo, final Host host) { super(); this.host = Args.notNull(host, "Host"); this.userInfo = userInfo; } /** * Constructs a new instance. * * @param host The host, never null. * @throws NullPointerException if the {@code host} is {@code null}. * @since 5.2 */ public URIAuthority(final Host host) { this(null, host); } /** * Constructs a new instance. * * @param userInfo The user info, may be null. * @param endpoint The named end-point, never null. * @throws NullPointerException If the end-point is {@code null}. * @throws NullPointerException If the end-point {@code name} is {@code null}. * @throws IllegalArgumentException If the end-point port is outside the specified range of valid port values, which is between 0 and 65535, inclusive. * {@code -1} indicates the scheme default port. * @since 5.2 */ public URIAuthority(final String userInfo, final NamedEndpoint endpoint) { super(); Args.notNull(endpoint, "Endpoint"); this.userInfo = userInfo; this.host = new Host(endpoint.getHostName(), endpoint.getPort()); } /** * Constructs a new instance. * * @param namedEndpoint The named end-point, never null. * @throws NullPointerException If the end-point is {@code null}. * @throws NullPointerException If the end-point {@code name} is {@code null}. * @throws IllegalArgumentException If the end-point port is outside the specified range of valid port values, which is between 0 and 65535, inclusive. * {@code -1} indicates the scheme default port. */ public URIAuthority(final NamedEndpoint namedEndpoint) { this(null, namedEndpoint); } /** * Creates a {@code URIAuthority} instance from a string. Text may not contain any blanks. * * @param s The value to parse * @return The parsed URIAuthority. * @throws URISyntaxException Thrown if a string could not be parsed as a URIAuthority. */ public static URIAuthority create(final String s) throws URISyntaxException { if (TextUtils.isBlank(s)) { return null; } final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); final URIAuthority uriAuthority = parse(s, cursor); if (!cursor.atEnd()) { throw URISupport.createException(s, cursor, "Unexpected content"); } return uriAuthority; } /** * Constructs a new instance. * * @param hostName The host name, not null. * @throws NullPointerException If the {@code name} is {@code null}. */ public URIAuthority(final String hostName) { this(null, hostName, -1); } /** * Gets the user info String. * * @return the user info String. */ public String getUserInfo() { return userInfo; } @Override public String getHostName() { return host.getHostName(); } @Override public int getPort() { return host.getPort(); } @Override public String toString() { return format(this); } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj instanceof URIAuthority) { final URIAuthority that = (URIAuthority) obj; return Objects.equals(this.userInfo, that.userInfo) && Objects.equals(this.host, that.host); } return false; } @Override public int hashCode() { int hash = LangUtils.HASH_SEED; hash = LangUtils.hashCode(hash, userInfo); hash = LangUtils.hashCode(hash, host); return hash; } } httpcore5/src/main/java/org/apache/hc/core5/net/URIBuilder.java0100644 0000000 0000000 00000117267 00000250600 023170 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.net; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import java.net.UnknownHostException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Collections; import java.util.LinkedList; import java.util.List; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.message.BasicNameValuePair; import org.apache.hc.core5.http.message.ParserCursor; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.util.Tokenizer; /** * Builder for {@link URI} instances. * * @since 5.0 */ public class URIBuilder { /** * Creates a new builder for the host {@link InetAddress#getLocalHost()}. * * @return a new builder. * @throws UnknownHostException if the local host name could not be resolved into an address. */ public static URIBuilder localhost() throws UnknownHostException { return new URIBuilder().setHost(InetAddress.getLocalHost()); } /** * Creates a new builder for the host {@link InetAddress#getLoopbackAddress()}. */ public static URIBuilder loopbackAddress() { return new URIBuilder().setHost(InetAddress.getLoopbackAddress()); } private String scheme; private String encodedSchemeSpecificPart; private String encodedAuthority; private String userInfo; private String encodedUserInfo; private String host; private int port; private String encodedPath; private boolean pathRootless; private List pathSegments; private String encodedQuery; private List queryParams; private String query; private Charset charset; private String fragment; private String encodedFragment; private EncodingPolicy encodingPolicy = EncodingPolicy.ALL_RESERVED; private boolean plusAsBlank; /** * Defines the encoding policy for URI components in {@link URIBuilder}. * This enum controls how characters are percent-encoded when constructing a URI, * allowing flexibility between strict encoding and RFC 3986-compliant behavior. * * @since 5.4 */ public enum EncodingPolicy { /** * Encodes all reserved characters, allowing only unreserved characters * (ALPHA, DIGIT, "-", ".", "_", "~") to remain unencoded. This is a strict * policy suitable for conservative URI production where maximum encoding * is desired. */ ALL_RESERVED, /** * Follows RFC 3986 component-specific encoding rules. For example, query and * fragment components allow unreserved characters, sub-delimiters ("!", "$", * "&", "'", "(", ")", "*", "+", ",", ";", "="), and additional characters * (":", "@", "/", "?") to remain unencoded, as defined by {@code PercentCodec.FRAGMENT}. * This policy ensures compliance with RFC 3986 while maintaining interoperability. */ RFC_3986 } /** * Constructs an empty instance. */ public URIBuilder() { super(); this.port = -1; } /** * Constructs an instance from the string which must be a valid URI. * * @param uriString a valid URI in string form. * @throws URISyntaxException if the input is not a valid URI. */ public URIBuilder(final String uriString) throws URISyntaxException { this(new URI(uriString), StandardCharsets.UTF_8); } /** * Constructs an instance from the provided URI. * @param uri a URI. */ public URIBuilder(final URI uri) { this(uri, StandardCharsets.UTF_8); } /** * Constructs an instance from the string which must be a valid URI. * * @param uriString a valid URI in string form. * @throws URISyntaxException if the input is not a valid URI */ public URIBuilder(final String uriString, final Charset charset) throws URISyntaxException { this(new URI(uriString), charset); } /** * Constructs an instance from the provided URI. * * @param uri a URI. */ public URIBuilder(final URI uri, final Charset charset) { super(); digestURI(uri, charset); } /** * Sets the authority. * * @param authority the authority. * @return this instance. * @since 5.2 */ public URIBuilder setAuthority(final NamedEndpoint authority) { setUserInfo(null); setHost(authority.getHostName()); setPort(authority.getPort()); return this; } /** * Sets the authority. * * @param authority the authority. * @return this instance. * @since 5.2 */ public URIBuilder setAuthority(final URIAuthority authority) { setUserInfo(authority.getUserInfo()); setHost(authority.getHostName()); setPort(authority.getPort()); return this; } /** * Sets the Charset. * * @param charset the Charset. * @return this instance. */ public URIBuilder setCharset(final Charset charset) { this.charset = charset; return this; } /** * Sets the encoding policy for this {@link URIBuilder}. * The encoding policy determines how URI components (e.g., query, fragment) are * percent-encoded when building the URI string. If not set, the default policy * is {@link EncodingPolicy#RFC_3986}. * * @param encodingPolicy the encoding policy to apply, or {@code null} to reset * to the default ({@link EncodingPolicy#ALL_RESERVED}) * @return this {@link URIBuilder} instance for method chaining * @since 5.4 */ public URIBuilder setEncodingPolicy(final EncodingPolicy encodingPolicy) { this.encodingPolicy = encodingPolicy; return this; } /** * Gets the authority. * * @return the authority. * @since 5.2 */ public URIAuthority getAuthority() { return new URIAuthority(getUserInfo(), getHost(), getPort()); } /** * Gets the Charset. * * @return the Charset. */ public Charset getCharset() { return charset; } /** * Sets whether the plus sign ('+') should be interpreted as a blank space (' ') when parsing * the query parameters of the URI. *

* In HTTP URLs, query strings may contain spaces encoded as '+' characters or as '%20'. * This flag controls whether '+' is interpreted as a space or remains as a plus sign. *

* *

* If the query string was already set, calling this method will re-parse the query * using the updated flag. This ensures that the query parameters are processed correctly * based on the specified interpretation of the '+' character. *

* * @param plusAsBlank {@code true} to interpret '+' as a space, {@code false} to keep '+' as a literal plus sign. * @return this {@link URIBuilder} instance for method chaining. * @since 5.4 */ public URIBuilder setPlusAsBlank(final boolean plusAsBlank) { this.plusAsBlank = plusAsBlank; // Re-parse the query string using the updated flag if (this.encodedQuery != null) { this.queryParams = parseQuery(this.encodedQuery, this.charset, this.plusAsBlank); } return this; } private static final char QUERY_PARAM_SEPARATOR = '&'; private static final char PARAM_VALUE_SEPARATOR = '='; private static final char PATH_SEPARATOR = '/'; private static final Tokenizer.Delimiter QUERY_PARAM_SEPARATORS = Tokenizer.delimiters(QUERY_PARAM_SEPARATOR, PARAM_VALUE_SEPARATOR); private static final Tokenizer.Delimiter QUERY_VALUE_SEPARATORS = Tokenizer.delimiters(QUERY_PARAM_SEPARATOR); static List parseQuery(final CharSequence s, final Charset charset, final boolean plusAsBlank) { if (s == null) { return null; } final Tokenizer tokenParser = Tokenizer.INSTANCE; final ParserCursor cursor = new ParserCursor(0, s.length()); final List list = new ArrayList<>(); while (!cursor.atEnd()) { final String name = tokenParser.parseToken(s, cursor, QUERY_PARAM_SEPARATORS); String value = null; if (!cursor.atEnd()) { final int delim = s.charAt(cursor.getPos()); cursor.updatePos(cursor.getPos() + 1); if (delim == PARAM_VALUE_SEPARATOR) { value = tokenParser.parseToken(s, cursor, QUERY_VALUE_SEPARATORS); if (!cursor.atEnd()) { cursor.updatePos(cursor.getPos() + 1); } } } if (!name.isEmpty()) { list.add(new BasicNameValuePair( PercentCodec.decode(name, charset, plusAsBlank), PercentCodec.decode(value, charset, plusAsBlank))); } } return list; } static List splitPath(final CharSequence s) { if (s == null) { return Collections.emptyList(); } final ParserCursor cursor = new ParserCursor(0, s.length()); // Skip leading separator if (cursor.atEnd()) { return new ArrayList<>(0); } if (s.charAt(cursor.getPos()) == PATH_SEPARATOR) { cursor.updatePos(cursor.getPos() + 1); } final List list = new ArrayList<>(); final StringBuilder buf = new StringBuilder(); for (;;) { if (cursor.atEnd()) { list.add(buf.toString()); break; } final char current = s.charAt(cursor.getPos()); if (current == PATH_SEPARATOR) { list.add(buf.toString()); buf.setLength(0); } else { buf.append(current); } cursor.updatePos(cursor.getPos() + 1); } return list; } static List parsePath(final CharSequence s, final Charset charset) { if (s == null) { return Collections.emptyList(); } final List segments = splitPath(s); final List list = new ArrayList<>(segments.size()); for (final String segment: segments) { list.add(PercentCodec.decode(segment, charset)); } return list; } static void formatPath(final StringBuilder buf, final Iterable segments, final boolean rootless, final Charset charset, final BitSet safechars) { int i = 0; for (final String segment : segments) { if (i > 0 || !rootless) { buf.append(PATH_SEPARATOR); } PercentCodec.encode(buf, segment, charset, safechars, false); i++; } } static void formatPath(final StringBuilder buf, final Iterable segments, final boolean rootless, final Charset charset) { formatPath(buf, segments, rootless, charset, PercentCodec.UNRESERVED); } static void formatQuery(final StringBuilder buf, final Iterable params, final Charset charset, final BitSet safechars, final boolean blankAsPlus) { int i = 0; for (final NameValuePair parameter : params) { if (i > 0) { buf.append(QUERY_PARAM_SEPARATOR); } PercentCodec.encode(buf, parameter.getName(), charset, safechars, blankAsPlus); if (parameter.getValue() != null) { buf.append(PARAM_VALUE_SEPARATOR); PercentCodec.encode(buf, parameter.getValue(), charset, safechars, blankAsPlus); } i++; } } static void formatQuery(final StringBuilder buf, final Iterable params, final Charset charset, final boolean blankAsPlus) { formatQuery(buf, params, charset, PercentCodec.UNRESERVED, blankAsPlus); } /** * Builds a {@link URI} instance. */ public URI build() throws URISyntaxException { if ((URIScheme.HTTPS.same(scheme) || URIScheme.HTTP.same(scheme)) && (TextUtils.isBlank(host))) { throw new URISyntaxException(scheme, "http/https URI cannot have an empty host identifier"); } return new URI(buildString()); } private String buildString() { final StringBuilder sb = new StringBuilder(); if (this.scheme != null) { sb.append(this.scheme).append(':'); } if (this.encodedSchemeSpecificPart != null) { sb.append(this.encodedSchemeSpecificPart); } else { final boolean authoritySpecified; if (this.encodedAuthority != null) { sb.append("//").append(this.encodedAuthority); authoritySpecified = true; } else if (this.host != null) { sb.append("//"); if (this.encodedUserInfo != null) { sb.append(this.encodedUserInfo).append("@"); } else if (this.userInfo != null) { final int idx = this.userInfo.indexOf(':'); if (idx != -1) { PercentCodec.encode(sb, this.userInfo.substring(0, idx), this.charset, encodingPolicy == EncodingPolicy.ALL_RESERVED ? PercentCodec.UNRESERVED : PercentCodec.USERINFO, false); sb.append(':'); PercentCodec.encode(sb, this.userInfo.substring(idx + 1), this.charset, encodingPolicy == EncodingPolicy.ALL_RESERVED ? PercentCodec.UNRESERVED : PercentCodec.USERINFO, false); } else { PercentCodec.encode(sb, this.userInfo, this.charset, encodingPolicy == EncodingPolicy.ALL_RESERVED ? PercentCodec.UNRESERVED : PercentCodec.USERINFO, false); } sb.append("@"); } if (InetAddressUtils.isIPv6(this.host)) { sb.append("[").append(this.host).append("]"); } else { PercentCodec.encode(sb, this.host, this.charset, encodingPolicy == EncodingPolicy.ALL_RESERVED ? PercentCodec.UNRESERVED : PercentCodec.REG_NAME, false); } if (this.port >= 0) { sb.append(":").append(this.port); } authoritySpecified = true; } else { authoritySpecified = false; } if (this.encodedPath != null) { if (authoritySpecified && !TextUtils.isEmpty(this.encodedPath) && !this.encodedPath.startsWith("/")) { sb.append('/'); } sb.append(this.encodedPath); } else if (this.pathSegments != null) { formatPath(sb, this.pathSegments, !authoritySpecified && this.pathRootless, this.charset, encodingPolicy == EncodingPolicy.ALL_RESERVED ? PercentCodec.UNRESERVED : PercentCodec.PATH_SEGMENT); } if (this.encodedQuery != null) { sb.append("?").append(this.encodedQuery); } else if (this.queryParams != null && !this.queryParams.isEmpty()) { sb.append("?"); formatQuery(sb, this.queryParams, this.charset, encodingPolicy == EncodingPolicy.ALL_RESERVED ? PercentCodec.UNRESERVED : PercentCodec.QUERY, false); } else if (this.query != null) { sb.append("?"); PercentCodec.encode(sb, this.query, this.charset, encodingPolicy == EncodingPolicy.ALL_RESERVED ? PercentCodec.URIC : PercentCodec.QUERY, false); } } if (this.encodedFragment != null) { sb.append("#").append(this.encodedFragment); } else if (this.fragment != null) { sb.append("#"); PercentCodec.encode(sb, this.fragment, this.charset, encodingPolicy == EncodingPolicy.ALL_RESERVED ? PercentCodec.URIC : PercentCodec.FRAGMENT, false); } return sb.toString(); } private void digestURI(final URI uri, final Charset charset) { this.scheme = uri.getScheme(); this.encodedSchemeSpecificPart = uri.getRawSchemeSpecificPart(); this.encodedAuthority = uri.getRawAuthority(); final String uriHost = uri.getHost(); // URI.getHost incorrectly returns bracketed (encoded) IPv6 values. Brackets are an // encoding detail of the URI and not part of the host string. this.host = uriHost != null && InetAddressUtils.isIPv6URLBracketed(uriHost) ? uriHost.substring(1, uriHost.length() - 1) : uriHost; this.port = uri.getPort(); this.encodedUserInfo = uri.getRawUserInfo(); this.userInfo = uri.getUserInfo(); if (this.encodedAuthority != null && this.host == null) { try { final URIAuthority uriAuthority = URIAuthority.parse(this.encodedAuthority); this.encodedUserInfo = uriAuthority.getUserInfo(); this.userInfo = PercentCodec.decode(uriAuthority.getUserInfo(), charset); this.host = PercentCodec.decode(uriAuthority.getHostName(), charset); this.port = uriAuthority.getPort(); } catch (final URISyntaxException ignore) { // ignore } } this.encodedPath = uri.getRawPath(); this.pathSegments = parsePath(uri.getRawPath(), charset); this.pathRootless = uri.getRawPath() == null || !uri.getRawPath().startsWith("/"); this.encodedQuery = uri.getRawQuery(); this.queryParams = parseQuery(uri.getRawQuery(), charset, this.plusAsBlank); this.encodedFragment = uri.getRawFragment(); this.fragment = uri.getFragment(); this.charset = charset; } /** * Sets URI scheme. * * @return this instance. */ public URIBuilder setScheme(final String scheme) { this.scheme = !TextUtils.isBlank(scheme) ? scheme : null; return this; } /** * Sets the URI scheme specific part. * * @param schemeSpecificPart * @return this instance. * @since 5.1 */ public URIBuilder setSchemeSpecificPart(final String schemeSpecificPart) { this.encodedSchemeSpecificPart = schemeSpecificPart; return this; } /** * Sets the URI scheme specific part and append a variable arguments list of NameValuePair instance(s) to this part. * * @param schemeSpecificPart * @param nvps Optional, can be null. Variable arguments list of NameValuePair query parameters to be reused by the specific scheme part * @return this instance. * @since 5.1 */ public URIBuilder setSchemeSpecificPart(final String schemeSpecificPart, final NameValuePair... nvps) { return setSchemeSpecificPart(schemeSpecificPart, nvps != null ? Arrays.asList(nvps) : null); } /** * Sets the URI scheme specific part and append a list of NameValuePair to this part. * * @param schemeSpecificPart * @param nvps Optional, can be null. List of query parameters to be reused by the specific scheme part * @return this instance. * @since 5.1 */ public URIBuilder setSchemeSpecificPart(final String schemeSpecificPart, final List nvps) { this.encodedSchemeSpecificPart = null; if (!TextUtils.isBlank(schemeSpecificPart)) { final StringBuilder sb = new StringBuilder(schemeSpecificPart); if (nvps != null && !nvps.isEmpty()) { sb.append("?"); formatQuery(sb, nvps, this.charset, false); } this.encodedSchemeSpecificPart = sb.toString(); } return this; } /** * Sets URI user info. The value is expected to be unescaped and may contain non ASCII * characters. * * @return this instance. */ public URIBuilder setUserInfo(final String userInfo) { this.userInfo = !TextUtils.isBlank(userInfo) ? userInfo : null; this.encodedSchemeSpecificPart = null; this.encodedAuthority = null; this.encodedUserInfo = null; return this; } /** * Sets URI user info as a combination of username and password. These values are expected to * be unescaped and may contain non ASCII characters. * * @return this instance. * * @deprecated The use of clear-text passwords in {@link URI}s has been deprecated and is strongly * discouraged. */ @Deprecated public URIBuilder setUserInfo(final String username, final String password) { return setUserInfo(username + ':' + password); } /** * Sets URI host. * * @return this instance. */ public URIBuilder setHost(final InetAddress host) { this.host = host != null ? host.getHostAddress() : null; this.encodedSchemeSpecificPart = null; this.encodedAuthority = null; return this; } /** * Sets URI host. The input value must not already be URI encoded, for example {@code ::1} is valid however * {@code [::1]} is not. It is dangerous to call {@code uriBuilder.setHost(uri.getHost())} due * to {@link URI#getHost()} returning URI encoded values. * * @return this instance. */ public URIBuilder setHost(final String host) { this.host = host; this.encodedSchemeSpecificPart = null; this.encodedAuthority = null; return this; } /** * Sets the scheme, host name, and port. * * @param httpHost the scheme, host name, and port. * @return this instance. */ public URIBuilder setHttpHost(final HttpHost httpHost) { setScheme(httpHost.getSchemeName()); setHost(httpHost.getHostName()); setPort(httpHost.getPort()); return this; } /** * Sets URI port. * * @return this instance. */ public URIBuilder setPort(final int port) { this.port = port < 0 ? -1 : port; this.encodedSchemeSpecificPart = null; this.encodedAuthority = null; return this; } /** * Sets URI path. The value is expected to be unescaped and may contain non ASCII characters. * * @return this instance. */ public URIBuilder setPath(final String path) { setPathSegments(path != null ? splitPath(path) : null); this.pathRootless = path != null && !path.startsWith("/"); return this; } /** * Appends path to URI. The value is expected to be unescaped and may contain non ASCII characters. * * @return this instance. */ public URIBuilder appendPath(final String path) { if (path != null) { appendPathSegments(splitPath(path)); } return this; } /** * Sets URI path. The value is expected to be unescaped and may contain non ASCII characters. * * @return this instance. */ public URIBuilder setPathSegments(final String... pathSegments) { return setPathSegments(Arrays.asList(pathSegments)); } /** * Appends segments URI path. The value is expected to be unescaped and may contain non ASCII characters. * * @return this instance. */ public URIBuilder appendPathSegments(final String... pathSegments) { return appendPathSegments(Arrays.asList(pathSegments)); } /** * Sets rootless URI path (the first segment does not start with a /). * The value is expected to be unescaped and may contain non ASCII characters. * * @return this instance. * * @since 5.1 */ public URIBuilder setPathSegmentsRootless(final String... pathSegments) { return setPathSegmentsRootless(Arrays.asList(pathSegments)); } /** * Sets URI path. The value is expected to be unescaped and may contain non ASCII characters. * * @return this instance. */ public URIBuilder setPathSegments(final List pathSegments) { this.pathSegments = pathSegments != null && !pathSegments.isEmpty() ? new ArrayList<>(pathSegments) : null; this.encodedSchemeSpecificPart = null; this.encodedPath = null; this.pathRootless = false; return this; } /** * Appends segments to URI path. The value is expected to be unescaped and may contain non ASCII characters. * * @return this instance. */ public URIBuilder appendPathSegments(final List pathSegments) { if (pathSegments != null && !pathSegments.isEmpty()) { if (this.pathSegments == null) { this.pathSegments = new ArrayList<>(); } this.pathSegments.addAll(pathSegments); this.encodedSchemeSpecificPart = null; this.encodedPath = null; } return this; } /** * Sets rootless URI path (the first segment does not start with a /). * The value is expected to be unescaped and may contain non ASCII characters. * * @return this instance. * * @since 5.1 */ public URIBuilder setPathSegmentsRootless(final List pathSegments) { this.pathSegments = pathSegments != null && !pathSegments.isEmpty() ? new ArrayList<>(pathSegments) : null; this.encodedSchemeSpecificPart = null; this.encodedPath = null; this.pathRootless = true; return this; } /** * Removes URI query. * * @return this instance. */ public URIBuilder removeQuery() { this.queryParams = null; this.query = null; this.encodedQuery = null; this.encodedSchemeSpecificPart = null; return this; } /** * Sets URI query parameters. The parameter name / values are expected to be unescaped * and may contain non ASCII characters. *

* Please note query parameters and custom query component are mutually exclusive. This method * will remove custom query if present. *

* * @return this instance. */ public URIBuilder setParameters(final List nameValuePairs) { if (this.queryParams == null) { this.queryParams = new ArrayList<>(); } else { this.queryParams.clear(); } if (nameValuePairs != null) { this.queryParams.addAll(nameValuePairs); } this.encodedQuery = null; this.encodedSchemeSpecificPart = null; this.query = null; return this; } /** * Adds URI query parameters. The parameter name / values are expected to be unescaped * and may contain non ASCII characters. *

* Please note query parameters and custom query component are mutually exclusive. This method * will remove custom query if present. *

* * @return this instance. */ public URIBuilder addParameters(final List nameValuePairs) { if (this.queryParams == null) { this.queryParams = new ArrayList<>(); } if (nameValuePairs != null) { this.queryParams.addAll(nameValuePairs); } this.encodedQuery = null; this.encodedSchemeSpecificPart = null; this.query = null; return this; } /** * Sets URI query parameters. The parameter name / values are expected to be unescaped * and may contain non ASCII characters. *

* Please note query parameters and custom query component are mutually exclusive. This method * will remove custom query if present. *

* * @return this instance. */ public URIBuilder setParameters(final NameValuePair... nameValuePairs) { if (this.queryParams == null) { this.queryParams = new ArrayList<>(); } else { this.queryParams.clear(); } if (nameValuePairs != null) { Collections.addAll(this.queryParams, nameValuePairs); } this.encodedQuery = null; this.encodedSchemeSpecificPart = null; this.query = null; return this; } /** * Adds parameter to URI query. The parameter name and value are expected to be unescaped * and may contain non ASCII characters. *

* Please note query parameters and custom query component are mutually exclusive. This method * will remove custom query if present. *

* * @return this instance. */ public URIBuilder addParameter(final String param, final String value) { return addParameter(new BasicNameValuePair(param, value)); } /** * Adds parameter to URI query. The parameter name and value are expected to be unescaped * and may contain non ASCII characters. *

* Please note query parameters and custom query component are mutually exclusive. This method * will remove custom query if present. *

* * @return this instance. * @since 5.2 */ public URIBuilder addParameter(final NameValuePair nameValuePair) { if (this.queryParams == null) { this.queryParams = new ArrayList<>(); } if (nameValuePair != null) { this.queryParams.add(nameValuePair); } this.encodedQuery = null; this.encodedSchemeSpecificPart = null; this.query = null; return this; } /** * Removes parameter of URI query if set. The parameter name is expected to be unescaped and may * contain non ASCII characters. *

* Please note query parameters and custom query component are mutually exclusive. This method * will remove custom query if present, even when no parameter was actually removed. *

* * @return this instance. * @since 5.2 */ public URIBuilder removeParameter(final String param) { Args.notNull(param, "param"); if (this.queryParams != null && !this.queryParams.isEmpty()) { this.queryParams.removeIf(nvp -> nvp.getName().equals(param)); } this.encodedQuery = null; this.encodedSchemeSpecificPart = null; this.query = null; return this; } /** * Sets parameter of URI query overriding existing value if set. The parameter name and value * are expected to be unescaped and may contain non ASCII characters. *

* Please note query parameters and custom query component are mutually exclusive. This method * will remove custom query if present. *

* * @return this instance. */ public URIBuilder setParameter(final String param, final String value) { if (this.queryParams == null) { this.queryParams = new ArrayList<>(); } if (!this.queryParams.isEmpty()) { this.queryParams.removeIf(nvp -> nvp.getName().equals(param)); } this.queryParams.add(new BasicNameValuePair(param, value)); this.encodedQuery = null; this.encodedSchemeSpecificPart = null; this.query = null; return this; } /** * Clears URI query parameters. * * @return this instance. */ public URIBuilder clearParameters() { this.queryParams = null; this.encodedQuery = null; this.encodedSchemeSpecificPart = null; return this; } /** * Sets custom URI query. The value is expected to be unescaped and may contain non ASCII * characters. *

* Please note query parameters and custom query component are mutually exclusive. This method * will remove query parameters if present. *

* * @return this instance. */ public URIBuilder setCustomQuery(final String query) { this.query = !TextUtils.isBlank(query) ? query : null; this.encodedQuery = null; this.encodedSchemeSpecificPart = null; this.queryParams = null; return this; } /** * Sets URI fragment. The value is expected to be unescaped and may contain non ASCII * characters. * * @return this instance. */ public URIBuilder setFragment(final String fragment) { this.fragment = !TextUtils.isBlank(fragment) ? fragment : null; this.encodedFragment = null; return this; } /** * Tests whether the URI is absolute. * * @return whether the URI is absolute. */ public boolean isAbsolute() { return this.scheme != null; } /** * Tests whether the URI is opaque. * * @return whether the URI is opaque. */ public boolean isOpaque() { return this.pathSegments == null && this.encodedPath == null; } /** * Gets the scheme. * * @return the scheme. */ public String getScheme() { return this.scheme; } /** * Gets the scheme specific part. * * @return String * @since 5.1 */ public String getSchemeSpecificPart() { return this.encodedSchemeSpecificPart; } /** * Gets the user info. * * @return the user info. */ public String getUserInfo() { return this.userInfo; } /** * Gets the host portion of the {@link URI}. This method returns unencoded IPv6 addresses (without brackets). * This behavior differs from values returned by {@link URI#getHost()}. * * @return The host portion of the URI. */ public String getHost() { return this.host; } /** * Gets the port. * * @return the port. */ public int getPort() { return this.port; } /** * Tests whether the path is empty. * * @return whether the path is empty. */ public boolean isPathEmpty() { return (this.pathSegments == null || this.pathSegments.isEmpty()) && (this.encodedPath == null || this.encodedPath.isEmpty()); } /** * Gets the path segments. * * @return the path segments. */ public List getPathSegments() { return this.pathSegments != null ? new ArrayList<>(this.pathSegments) : new ArrayList<>(); } /** * Gets the path. * * @return the path. */ public String getPath() { if (this.pathSegments == null) { return null; } final StringBuilder result = new StringBuilder(); for (final String segment : this.pathSegments) { result.append('/').append(segment); } return result.toString(); } /** * Tests whether the query is empty. * * @return whether the query is empty. */ public boolean isQueryEmpty() { return (this.queryParams == null || this.queryParams.isEmpty()) && this.encodedQuery == null; } /** * Gets the query parameters as a List. * * @return the query parameters as a List. */ public List getQueryParams() { return this.queryParams != null ? new ArrayList<>(this.queryParams) : new ArrayList<>(); } /** * Gets the first {@link NameValuePair} for a given name. * * @param name the name * @return the first named {@link NameValuePair} or null if not found. * @since 5.2 */ public NameValuePair getFirstQueryParam(final String name) { if (queryParams == null) { return null; } return queryParams.stream().filter(e -> name.equals(e.getName())).findFirst().orElse(null); } /** * Gets the fragments. * * @return the fragments. */ public String getFragment() { return this.fragment; } /** * @deprecated do not use this method. * * @see #optimize() */ @Deprecated public URIBuilder normalizeSyntax() { return optimize(); } /** * Optimizes URI components if the URI is considered non-opaque (the path component has a root): *
    *
  • characters of scheme and host components are converted to lower case
  • *
  • dot segments of the path component are removed if the path has a root
  • *
  • percent encoding of all components is re-applied
  • *
*

* Please note some URI consumers may consider the optimized URI components produced * by this method as semantically different from the original ones. * * @since 5.3 */ public URIBuilder optimize() { final String scheme = this.scheme; if (scheme != null) { this.scheme = TextUtils.toLowerCase(scheme); } if (this.pathRootless) { return this; } // Force Percent-Encoding re-encoding this.encodedSchemeSpecificPart = null; this.encodedAuthority = null; this.encodedUserInfo = null; this.encodedPath = null; this.encodedQuery = null; this.encodedFragment = null; final String host = this.host; if (host != null) { this.host = TextUtils.toLowerCase(host); } if (this.pathSegments != null) { final List inputSegments = this.pathSegments; if (!inputSegments.isEmpty()) { final LinkedList outputSegments = new LinkedList<>(); for (final String inputSegment : inputSegments) { if (!inputSegment.isEmpty() && !".".equals(inputSegment)) { if ("..".equals(inputSegment)) { if (!outputSegments.isEmpty()) { outputSegments.removeLast(); } } else { outputSegments.addLast(inputSegment); } } } if (!inputSegments.isEmpty()) { final String lastSegment = inputSegments.get(inputSegments.size() - 1); if (lastSegment.isEmpty()) { outputSegments.addLast(""); } } this.pathSegments = outputSegments; } else { this.pathSegments = Collections.singletonList(""); } } return this; } /** * Converts this instance to a URI string. * * @return this instance to a URI string. */ @Override public String toString() { return buildString(); } } httpcore5/src/main/java/org/apache/hc/core5/net/URISupport.java0100644 0000000 0000000 00000004714 00000250600 023246 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.net; import java.net.URISyntaxException; import java.util.BitSet; import org.apache.hc.core5.util.Tokenizer; final class URISupport { private static final BitSet HOST_SEPARATORS = new BitSet(256); private static final BitSet PORT_SEPARATORS = new BitSet(256); private static final BitSet TERMINATORS = new BitSet(256); static { TERMINATORS.set('/'); TERMINATORS.set('#'); TERMINATORS.set('?'); HOST_SEPARATORS.or(TERMINATORS); HOST_SEPARATORS.set('@'); PORT_SEPARATORS.or(TERMINATORS); PORT_SEPARATORS.set(':'); } static final Tokenizer.Delimiter DELIMITERS = Tokenizer.delimiters(TERMINATORS); static final Tokenizer.Delimiter HOST_DELIMITERS = Tokenizer.delimiters(HOST_SEPARATORS); static final Tokenizer.Delimiter IPV6_HOST_DELIMITERS = Tokenizer.delimiters(']'); static final Tokenizer.Delimiter PORT_DELIMITERS = Tokenizer.delimiters(PORT_SEPARATORS); static URISyntaxException createException( final CharSequence input, final Tokenizer.Cursor cursor, final String reason) { return new URISyntaxException( input.subSequence(cursor.getLowerBound(), cursor.getUpperBound()).toString(), reason, cursor.getPos()); } } httpcore5/src/main/java/org/apache/hc/core5/net/URLEncodedUtils.java0100644 0000000 0000000 00000021130 00000250600 024146 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.net; import java.net.URI; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.List; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.message.BasicNameValuePair; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Tokenizer; /** * A collection of utilities for encoding URLs. * * @since 4.0 * * @deprecated Use {@link URIBuilder} to parse and format {@link URI}s and * {@link WWWFormCodec} to parse and format {@code application/x-www-form-urlencoded} forms. */ @Deprecated public class URLEncodedUtils { private static final char QP_SEP_A = '&'; private static final char QP_SEP_S = ';'; /** * Returns a list of {@link NameValuePair}s URI query parameters. * By convention, {@code '&'} and {@code ';'} are accepted as parameter separators. * * @param uri input URI. * @param charset parameter charset. * @return list of query parameters. * * @since 4.5 */ public static List parse(final URI uri, final Charset charset) { Args.notNull(uri, "URI"); final String query = uri.getRawQuery(); if (query != null && !query.isEmpty()) { return parse(query, charset); } return new ArrayList<>(0); } /** * Returns a list of {@link NameValuePair}s URI query parameters. * By convention, {@code '&'} and {@code ';'} are accepted as parameter separators. * * @param s URI query component. * @param charset charset to use when decoding the parameters. * @return list of query parameters. * * @since 4.2 */ public static List parse(final CharSequence s, final Charset charset) { if (s == null) { return new ArrayList<>(0); } return parse(s, charset, QP_SEP_A, QP_SEP_S); } /** * Returns a list of {@link NameValuePair}s parameters. * * @param s input text. * @param charset parameter charset. * @param separators parameter separators. * @return list of query parameters. * * @since 4.4 */ public static List parse( final CharSequence s, final Charset charset, final char... separators) { Args.notNull(s, "Char sequence"); final Tokenizer tokenParser = Tokenizer.INSTANCE; final BitSet delimSet = new BitSet(); for (final char separator: separators) { delimSet.set(separator); } final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); final List list = new ArrayList<>(); while (!cursor.atEnd()) { delimSet.set('='); final String name = tokenParser.parseToken(s, cursor, delimSet); String value = null; if (!cursor.atEnd()) { final int delim = s.charAt(cursor.getPos()); cursor.updatePos(cursor.getPos() + 1); if (delim == '=') { delimSet.clear('='); value = tokenParser.parseToken(s, cursor, delimSet); if (!cursor.atEnd()) { cursor.updatePos(cursor.getPos() + 1); } } } if (!name.isEmpty()) { list.add(new BasicNameValuePair( PercentCodec.decode(name, charset, true), PercentCodec.decode(value, charset, true))); } } return list; } /** * Returns a list of URI path segments. * * @param s URI path component. * @param charset parameter charset. * @return list of segments. * * @since 4.5 */ public static List parsePathSegments(final CharSequence s, final Charset charset) { return URIBuilder.parsePath(s, charset); } /** * Returns a list of URI path segments. * * @param s URI path component. * @return list of segments. * * @since 4.5 */ public static List parsePathSegments(final CharSequence s) { return parsePathSegments(s, StandardCharsets.UTF_8); } /** * Returns a string consisting of joint encoded path segments. * * @param segments the segments. * @param charset parameter charset. * @return URI path component * * @since 4.5 */ public static String formatSegments(final Iterable segments, final Charset charset) { Args.notNull(segments, "Segments"); final StringBuilder buf = new StringBuilder(); URIBuilder.formatPath(buf, segments, false, charset); return buf.toString(); } /** * Returns a string consisting of joint encoded path segments. * * @param segments the segments. * @return URI path component * * @since 4.5 */ public static String formatSegments(final String... segments) { return formatSegments(Arrays.asList(segments), StandardCharsets.UTF_8); } /** * Returns a String that is suitable for use as an {@code application/x-www-form-urlencoded} * list of parameters in an HTTP PUT or HTTP POST. * * @param parameters The parameters to include. * @param parameterSeparator The parameter separator, by convention, {@code '&'} or {@code ';'}. * @param charset The encoding to use. * @return An {@code application/x-www-form-urlencoded} string * * @since 4.3 */ public static String format( final Iterable parameters, final char parameterSeparator, final Charset charset) { Args.notNull(parameters, "Parameters"); final StringBuilder buf = new StringBuilder(); int i = 0; for (final NameValuePair parameter : parameters) { if (i > 0) { buf.append(parameterSeparator); } PercentCodec.encode(buf, parameter.getName(), charset, URL_ENCODER, true); if (parameter.getValue() != null) { buf.append('='); PercentCodec.encode(buf, parameter.getValue(), charset, URL_ENCODER, true); } i++; } return buf.toString(); } /** * Returns a String that is suitable for use as an {@code application/x-www-form-urlencoded} * list of parameters in an HTTP PUT or HTTP POST. * * @param parameters The parameters to include. * @param charset The encoding to use. * @return An {@code application/x-www-form-urlencoded} string * * @since 4.2 */ public static String format( final Iterable parameters, final Charset charset) { return format(parameters, QP_SEP_A, charset); } private static final BitSet URL_ENCODER = new BitSet(256); static { // unreserved chars // alpha characters for (int i = 'a'; i <= 'z'; i++) { URL_ENCODER.set(i); } for (int i = 'A'; i <= 'Z'; i++) { URL_ENCODER.set(i); } // numeric characters for (int i = '0'; i <= '9'; i++) { URL_ENCODER.set(i); } URL_ENCODER.set('_'); // these are the characters of the "mark" list URL_ENCODER.set('-'); URL_ENCODER.set('.'); URL_ENCODER.set('*'); } } httpcore5/src/main/java/org/apache/hc/core5/net/WWWFormCodec.java0100644 0000000 0000000 00000005452 00000250600 023460 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.net; import java.nio.charset.Charset; import java.util.List; import org.apache.hc.core5.http.NameValuePair; /** * {@code application/x-www-form-urlencoded} codec. * * @since 5.1 */ public class WWWFormCodec { /** * Returns a list of {@link NameValuePair} parameters parsed * from the {@code application/x-www-form-urlencoded} content. * * @param s input text. * @param charset parameter charset. * @return list of form parameters. */ public static List parse(final CharSequence s, final Charset charset) { return URIBuilder.parseQuery(s, charset, true); } /** * Formats the list of {@link NameValuePair} parameters into a {@code application/x-www-form-urlencoded} * content. * * @param buf the content buffer * @param params The from parameters. * @param charset The encoding to use. */ public static void format( final StringBuilder buf, final Iterable params, final Charset charset) { URIBuilder.formatQuery(buf, params, charset, true); } /** * Formats the list of {@link NameValuePair} parameters into a {@code application/x-www-form-urlencoded} * content string. * * @param params The from parameters. * @param charset The encoding to use. * @return content string */ public static String format(final Iterable params, final Charset charset) { final StringBuilder buf = new StringBuilder(); URIBuilder.formatQuery(buf, params, charset, true); return buf.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/net/package-info.java0100644 0000000 0000000 00000002370 00000250600 023532 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Core network component APIs and utilities. */ package org.apache.hc.core5.net; httpcore5/src/main/java/org/apache/hc/core5/pool/0040755 0000000 0000000 00000000000 00000250600 020527 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/pool/ConnPool.java0100644 0000000 0000000 00000005667 00000250600 023134 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; import java.util.concurrent.Future; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.io.ModalCloseable; import org.apache.hc.core5.util.Timeout; /** * {@code ConnPool} represents a shared pool connections can be leased from * and released back to. * * @param the route type that represents the opposite endpoint of a pooled * connection. * @param the type of pooled connections. * @since 4.2 */ public interface ConnPool { /** * Attempts to lease a connection for the given route and with the given * state from the pool. *

* Please note the connection request can get automatically cancelled by the pool * in case of a request timeout. * * @param route route of the connection. * @param state arbitrary object that represents a particular state * (usually a security principal or a unique token identifying * the user whose credentials have been used while establishing the connection). * May be {@code null}. * @param requestTimeout request timeout. In case of a timeout the request * can get automatically cancelled by the pool. * @param callback operation completion callback. * * @return future for a leased pool entry. */ Future> lease(T route, Object state, Timeout requestTimeout, FutureCallback> callback); /** * Releases the pool entry back to the pool. * * @param entry pool entry leased from the pool * @param reusable flag indicating whether or not the released connection * is in a consistent state and is safe for further use. */ void release(PoolEntry entry, boolean reusable); } httpcore5/src/main/java/org/apache/hc/core5/pool/ConnPoolControl.java0100644 0000000 0000000 00000003630 00000250600 024461 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; import java.util.Set; import org.apache.hc.core5.util.TimeValue; /** * Interface to control runtime properties of a {@link ConnPool} such as * maximum total number of connections or maximum connections per route * allowed. * * @param the route type that represents the opposite endpoint of a pooled * connection. * @since 4.2 */ public interface ConnPoolControl extends ConnPoolStats { void setMaxTotal(int max); int getMaxTotal(); void setDefaultMaxPerRoute(int max); int getDefaultMaxPerRoute(); void setMaxPerRoute(final T route, int max); int getMaxPerRoute(final T route); void closeIdle(TimeValue idleTime); void closeExpired(); Set getRoutes(); } httpcore5/src/main/java/org/apache/hc/core5/pool/ConnPoolListener.java0100644 0000000 0000000 00000003233 00000250600 024625 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; /** * Connection pool event listener. * * @param the route type that represents the opposite endpoint of a pooled connection. * @since 5.0 */ @Contract(threading = ThreadingBehavior.STATELESS) public interface ConnPoolListener { void onLease(T route, ConnPoolStats connPoolStats); void onRelease(T route, ConnPoolStats connPoolStats); } httpcore5/src/main/java/org/apache/hc/core5/pool/ConnPoolStats.java0100644 0000000 0000000 00000002736 00000250600 024145 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; /** * Interface to obtain connection pool statistics. * * @param the route type that represents the opposite endpoint of a pooled * connection. * @since 4.2 */ public interface ConnPoolStats { PoolStats getTotalStats(); PoolStats getStats(final T route); } httpcore5/src/main/java/org/apache/hc/core5/pool/DefaultDisposalCallback.java0100644 0000000 0000000 00000004311 00000250600 026066 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.SocketModalCloseable; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; /** * Default implementation of {@link DisposalCallback}. * * @param socket based connection type. * * @since 5.0 */ @Internal public final class DefaultDisposalCallback implements DisposalCallback { private final static Timeout DEFAULT_CLOSE_TIMEOUT = Timeout.ofSeconds(1L); @Override public void execute(final SocketModalCloseable closeable, final CloseMode closeMode) { final Timeout socketTimeout = closeable.getSocketTimeout(); if (socketTimeout == null || socketTimeout.compareTo(TimeValue.ZERO_MILLISECONDS) <= 0 || socketTimeout.compareTo(DEFAULT_CLOSE_TIMEOUT) > 0) { closeable.setSocketTimeout(DEFAULT_CLOSE_TIMEOUT); } closeable.close(closeMode); } } httpcore5/src/main/java/org/apache/hc/core5/pool/DisposalCallback.java0100644 0000000 0000000 00000003154 00000250600 024565 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.ModalCloseable; /** * Represents a customizable disposal strategy for {@link ModalCloseable} instances. * * @param process or connection type. * * @since 5.0 */ @Internal public interface DisposalCallback { void execute(final T closeable, CloseMode closeMode); } httpcore5/src/main/java/org/apache/hc/core5/pool/LaxConnPool.java0100644 0000000 0000000 00000055700 00000250600 023572 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; import java.util.Deque; import java.util.HashSet; import java.util.Iterator; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentMap; 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.AtomicMarkableReference; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.concurrent.BasicFuture; import org.apache.hc.core5.concurrent.Cancellable; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.ModalCloseable; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; import org.apache.hc.core5.util.Deadline; import org.apache.hc.core5.util.DeadlineTimeoutException; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; /** * Connection pool with higher concurrency but with lax connection limit guarantees. * * @param route * @param connection object * * @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE) @Experimental public class LaxConnPool implements ManagedConnPool { private final TimeValue timeToLive; private final PoolReusePolicy policy; private final DisposalCallback disposalCallback; private final ConnPoolListener connPoolListener; private final ConcurrentMap> routeToPool; private final AtomicBoolean isShutDown; private volatile int defaultMaxPerRoute; /** * @since 5.0 */ public LaxConnPool( final int defaultMaxPerRoute, final TimeValue timeToLive, final PoolReusePolicy policy, final DisposalCallback disposalCallback, final ConnPoolListener connPoolListener) { super(); Args.positive(defaultMaxPerRoute, "Max per route value"); this.timeToLive = TimeValue.defaultsToNegativeOneMillisecond(timeToLive); this.policy = policy != null ? policy : PoolReusePolicy.LIFO; this.disposalCallback = disposalCallback; this.connPoolListener = connPoolListener; this.routeToPool = new ConcurrentHashMap<>(); this.isShutDown = new AtomicBoolean(); this.defaultMaxPerRoute = defaultMaxPerRoute; } /** * @since 5.0 */ public LaxConnPool( final int defaultMaxPerRoute, final TimeValue timeToLive, final PoolReusePolicy policy, final ConnPoolListener connPoolListener) { this(defaultMaxPerRoute, timeToLive, policy, null, connPoolListener); } public LaxConnPool(final int defaultMaxPerRoute) { this(defaultMaxPerRoute, TimeValue.NEG_ONE_MILLISECOND, PoolReusePolicy.LIFO, null, null); } public boolean isShutdown() { return isShutDown.get(); } @Override public void close(final CloseMode closeMode) { if (isShutDown.compareAndSet(false, true)) { for (final Iterator> it = routeToPool.values().iterator(); it.hasNext(); ) { final PerRoutePool routePool = it.next(); routePool.shutdown(closeMode); } routeToPool.clear(); } } @Override public void close() { close(CloseMode.GRACEFUL); } private PerRoutePool getPool(final T route) { PerRoutePool routePool = routeToPool.get(route); if (routePool == null) { final PerRoutePool newRoutePool = new PerRoutePool<>( route, defaultMaxPerRoute, timeToLive, policy, this, disposalCallback, connPoolListener); routePool = routeToPool.putIfAbsent(route, newRoutePool); if (routePool == null) { routePool = newRoutePool; } } return routePool; } @Override public Future> lease( final T route, final Object state, final Timeout requestTimeout, final FutureCallback> callback) { Args.notNull(route, "Route"); Asserts.check(!isShutDown.get(), "Connection pool shut down"); final PerRoutePool routePool = getPool(route); return routePool.lease(state, requestTimeout, callback); } public Future> lease(final T route, final Object state) { return lease(route, state, Timeout.DISABLED, null); } @Override public void release(final PoolEntry entry, final boolean reusable) { if (entry == null) { return; } if (isShutDown.get()) { return; } final PerRoutePool routePool = getPool(entry.getRoute()); routePool.release(entry, reusable); } public void validatePendingRequests() { for (final PerRoutePool routePool : routeToPool.values()) { routePool.validatePendingRequests(); } } @Override public void setMaxTotal(final int max) { } @Override public int getMaxTotal() { return 0; } @Override public void setDefaultMaxPerRoute(final int max) { Args.positive(max, "Max value"); defaultMaxPerRoute = max; } @Override public int getDefaultMaxPerRoute() { return defaultMaxPerRoute; } @Override public void setMaxPerRoute(final T route, final int max) { Args.notNull(route, "Route"); final PerRoutePool routePool = getPool(route); routePool.setMax(max > -1 ? max : defaultMaxPerRoute); } @Override public int getMaxPerRoute(final T route) { Args.notNull(route, "Route"); final PerRoutePool routePool = getPool(route); return routePool.getMax(); } @Override public PoolStats getTotalStats() { int leasedTotal = 0; int pendingTotal = 0; int availableTotal = 0; long maxTotal = 0; for (final PerRoutePool routePool : routeToPool.values()) { leasedTotal += routePool.getLeasedCount(); pendingTotal += routePool.getPendingCount(); availableTotal += routePool.getAvailableCount(); maxTotal += routePool.getMax(); } return new PoolStats(leasedTotal, pendingTotal, availableTotal, (int) Long.min(maxTotal, Integer.MAX_VALUE)); } @Override public PoolStats getStats(final T route) { Args.notNull(route, "Route"); final PerRoutePool routePool = getPool(route); return new PoolStats( routePool.getLeasedCount(), routePool.getPendingCount(), routePool.getAvailableCount(), routePool.getMax()); } @Override public Set getRoutes() { return new HashSet<>(routeToPool.keySet()); } public void enumAvailable(final Callback> callback) { for (final PerRoutePool routePool : routeToPool.values()) { routePool.enumAvailable(callback); } } public void enumLeased(final Callback> callback) { for (final PerRoutePool routePool : routeToPool.values()) { routePool.enumLeased(callback); } } @Override public void closeIdle(final TimeValue idleTime) { final long deadline = System.currentTimeMillis() - (TimeValue.isPositive(idleTime) ? idleTime.toMilliseconds() : 0); enumAvailable(entry -> { if (entry.getUpdated() <= deadline) { entry.discardConnection(CloseMode.GRACEFUL); } }); } @Override public void closeExpired() { final long now = System.currentTimeMillis(); enumAvailable(entry -> { if (entry.getExpiryDeadline().isBefore(now)) { entry.discardConnection(CloseMode.GRACEFUL); } }); } @Override public String toString() { final PoolStats totalStats = getTotalStats(); final StringBuilder buffer = new StringBuilder(); buffer.append("[leased: "); buffer.append(totalStats.getLeased()); buffer.append("][available: "); buffer.append(totalStats.getAvailable()); buffer.append("][pending: "); buffer.append(totalStats.getPending()); buffer.append("]"); return buffer.toString(); } static class LeaseRequest implements Cancellable { private final Object state; private final Deadline deadline; private final BasicFuture> future; LeaseRequest( final Object state, final Timeout requestTimeout, final BasicFuture> future) { super(); this.state = state; this.deadline = Deadline.calculate(requestTimeout); this.future = future; } BasicFuture> getFuture() { return this.future; } public Object getState() { return this.state; } public Deadline getDeadline() { return this.deadline; } public boolean isDone() { return this.future.isDone(); } public boolean completed(final PoolEntry result) { return future.completed(result); } public boolean failed(final Exception ex) { return future.failed(ex); } @Override public boolean cancel() { return future.cancel(); } } static class PerRoutePool { private enum RequestServiceStrategy { FIRST_SUCCESSFUL, ALL } private final T route; private final TimeValue timeToLive; private final PoolReusePolicy policy; private final DisposalCallback disposalCallback; private final ConnPoolListener connPoolListener; private final ConnPoolStats connPoolStats; private final ConcurrentMap, Boolean> leased; private final Deque>> available; private final Deque> pending; private final AtomicBoolean terminated; private final AtomicInteger allocated; private final AtomicLong releaseSeqNum; private volatile int max; PerRoutePool( final T route, final int max, final TimeValue timeToLive, final PoolReusePolicy policy, final ConnPoolStats connPoolStats, final DisposalCallback disposalCallback, final ConnPoolListener connPoolListener) { super(); this.route = route; this.timeToLive = timeToLive; this.policy = policy; this.connPoolStats = connPoolStats; this.disposalCallback = disposalCallback; this.connPoolListener = connPoolListener; this.leased = new ConcurrentHashMap<>(); this.available = new ConcurrentLinkedDeque<>(); this.pending = new ConcurrentLinkedDeque<>(); this.terminated = new AtomicBoolean(); this.allocated = new AtomicInteger(0); this.releaseSeqNum = new AtomicLong(0); this.max = max; } public void shutdown(final CloseMode closeMode) { if (terminated.compareAndSet(false, true)) { AtomicMarkableReference> entryRef; while ((entryRef = available.poll()) != null) { entryRef.getReference().discardConnection(closeMode); } for (final PoolEntry entry : leased.keySet()) { entry.discardConnection(closeMode); } leased.clear(); LeaseRequest leaseRequest; while ((leaseRequest = pending.poll()) != null) { leaseRequest.cancel(); } } } private PoolEntry createPoolEntry() { final int poolMax = max; int prev, next; do { prev = allocated.get(); next = (prev < poolMax) ? prev + 1 : prev; } while (!allocated.compareAndSet(prev, next)); return prev < next ? new PoolEntry<>(route, timeToLive, disposalCallback) : null; } private void deallocatePoolEntry() { allocated.decrementAndGet(); } private void addLeased(final PoolEntry entry) { if (leased.putIfAbsent(entry, Boolean.TRUE) != null) { throw new IllegalStateException("Pool entry already present in the set of leased entries"); } else if (connPoolListener != null) { connPoolListener.onLease(route, connPoolStats); } } private void removeLeased(final PoolEntry entry) { if (connPoolListener != null) { connPoolListener.onRelease(route, connPoolStats); } if (!leased.remove(entry, Boolean.TRUE)) { throw new IllegalStateException("Pool entry is not present in the set of leased entries"); } } private PoolEntry getAvailableEntry(final Object state) { for (final Iterator>> it = available.iterator(); it.hasNext(); ) { final AtomicMarkableReference> ref = it.next(); final PoolEntry entry = ref.getReference(); if (ref.compareAndSet(entry, entry, false, true)) { it.remove(); if (entry.getExpiryDeadline().isExpired() || !Objects.equals(entry.getState(), state)) { entry.discardConnection(CloseMode.GRACEFUL); deallocatePoolEntry(); } else { return entry; } } } return null; } public Future> lease( final Object state, final Timeout requestTimeout, final FutureCallback> callback) { Asserts.check(!terminated.get(), "Connection pool shut down"); final BasicFuture> future = new BasicFuture>(callback) { @Override public PoolEntry get( final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { try { return super.get(timeout, unit); } catch (final TimeoutException ex) { if (cancel()) { throw ex; } return super.get(); } } }; final long releaseState = releaseSeqNum.get(); PoolEntry entry = null; if (pending.isEmpty()) { entry = getAvailableEntry(state); if (entry == null) { entry = createPoolEntry(); } } if (entry != null) { addLeased(entry); future.completed(entry); } else { pending.add(new LeaseRequest<>(state, requestTimeout, future)); if (releaseState != releaseSeqNum.get()) { servicePendingRequest(); } } return future; } public void release(final PoolEntry releasedEntry, final boolean reusable) { removeLeased(releasedEntry); if (!reusable || releasedEntry.getExpiryDeadline().isExpired()) { releasedEntry.discardConnection(CloseMode.GRACEFUL); } if (releasedEntry.hasConnection()) { switch (policy) { case LIFO: available.addFirst(new AtomicMarkableReference<>(releasedEntry, false)); break; case FIFO: available.addLast(new AtomicMarkableReference<>(releasedEntry, false)); break; default: throw new IllegalStateException("Unexpected ConnPoolPolicy value: " + policy); } } else { deallocatePoolEntry(); } releaseSeqNum.incrementAndGet(); servicePendingRequest(); } private void servicePendingRequest() { servicePendingRequests(RequestServiceStrategy.FIRST_SUCCESSFUL); } private void servicePendingRequests(final RequestServiceStrategy serviceStrategy) { LeaseRequest leaseRequest; while ((leaseRequest = pending.poll()) != null) { if (leaseRequest.isDone()) { continue; } final Object state = leaseRequest.getState(); final Deadline deadline = leaseRequest.getDeadline(); if (deadline.isExpired()) { leaseRequest.failed(DeadlineTimeoutException.from(deadline)); } else { final long releaseState = releaseSeqNum.get(); PoolEntry entry = getAvailableEntry(state); if (entry == null) { entry = createPoolEntry(); } if (entry != null) { addLeased(entry); if (!leaseRequest.completed(entry)) { release(entry, true); } if (serviceStrategy == RequestServiceStrategy.FIRST_SUCCESSFUL) { break; } } else { pending.addFirst(leaseRequest); if (releaseState == releaseSeqNum.get()) { break; } } } } } public void validatePendingRequests() { final Iterator> it = pending.iterator(); while (it.hasNext()) { final LeaseRequest request = it.next(); final BasicFuture> future = request.getFuture(); if (future.isCancelled() && !request.isDone()) { it.remove(); } else { final Deadline deadline = request.getDeadline(); if (deadline.isExpired()) { request.failed(DeadlineTimeoutException.from(deadline)); } if (request.isDone()) { it.remove(); } } } } public final T getRoute() { return route; } public int getMax() { return max; } public void setMax(final int max) { this.max = max; } public int getPendingCount() { return pending.size(); } public int getLeasedCount() { return leased.size(); } public int getAvailableCount() { return available.size(); } public void enumAvailable(final Callback> callback) { for (final Iterator>> it = available.iterator(); it.hasNext(); ) { final AtomicMarkableReference> ref = it.next(); final PoolEntry entry = ref.getReference(); if (ref.compareAndSet(entry, entry, false, true)) { callback.execute(entry); if (!entry.hasConnection()) { deallocatePoolEntry(); it.remove(); } else { ref.set(entry, false); } } } releaseSeqNum.incrementAndGet(); servicePendingRequests(RequestServiceStrategy.ALL); } public void enumLeased(final Callback> callback) { for (final Iterator> it = leased.keySet().iterator(); it.hasNext(); ) { final PoolEntry entry = it.next(); callback.execute(entry); if (!entry.hasConnection()) { deallocatePoolEntry(); it.remove(); } } } @Override public String toString() { final StringBuilder buffer = new StringBuilder(); buffer.append("[route: "); buffer.append(route); buffer.append("][leased: "); buffer.append(leased.size()); buffer.append("][available: "); buffer.append(available.size()); buffer.append("][pending: "); buffer.append(pending.size()); buffer.append("]"); return buffer.toString(); } } } httpcore5/src/main/java/org/apache/hc/core5/pool/ManagedConnPool.java0100644 0000000 0000000 00000003163 00000250600 024376 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; import org.apache.hc.core5.io.ModalCloseable; /** * {@link ConnPool} that also implements {@link ConnPoolControl} and {@link AutoCloseable}. * * @param the route type that represents the opposite endpoint of a pooled * connection. * @param the type of pooled connections. * @since 4.2 */ public interface ManagedConnPool extends ConnPool, ConnPoolControl, ModalCloseable { } httpcore5/src/main/java/org/apache/hc/core5/pool/PoolConcurrencyPolicy.java0100644 0000000 0000000 00000003153 00000250600 025675 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; /** * Enumeration of pool concurrency policies * * @since 5.0 */ public enum PoolConcurrencyPolicy { /** * Higher concurrency but with lax connection max limit guarantees. */ LAX, /** * Strict connection max limit guarantees. */ STRICT, /** * Lock-free, route-segmented pool: avoids blocking leases during slow connection disposal. * @since 5.4 */ OFFLOCK } httpcore5/src/main/java/org/apache/hc/core5/pool/PoolEntry.java0100644 0000000 0000000 00000015467 00000250600 023337 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.ModalCloseable; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Deadline; import org.apache.hc.core5.util.TimeValue; /** * Pool entry containing a pool connection object along with its route. *

* The connection assigned to this pool entry may have an expiration time and also have an object * representing a connection state (usually a security principal or a unique token identifying * the user whose credentials have been used while establishing the connection). * * @param the route type that represents the opposite endpoint of a pooled * connection. * @param the connection type. * @since 4.2 */ public final class PoolEntry { private final T route; private final TimeValue timeToLive; private final AtomicReference connRef; private final DisposalCallback disposalCallback; private final Supplier currentTimeSupplier; private volatile Object state; private volatile long created; private volatile long updated; private volatile Deadline expiryDeadline = Deadline.MIN_VALUE; private volatile Deadline validityDeadline = Deadline.MIN_VALUE; PoolEntry(final T route, final TimeValue timeToLive, final DisposalCallback disposalCallback, final Supplier currentTimeSupplier) { super(); this.route = Args.notNull(route, "Route"); this.timeToLive = TimeValue.defaultsToNegativeOneMillisecond(timeToLive); this.connRef = new AtomicReference<>(); this.disposalCallback = disposalCallback; this.currentTimeSupplier = currentTimeSupplier; } PoolEntry(final T route, final TimeValue timeToLive, final Supplier currentTimeSupplier) { this(route, timeToLive, null, currentTimeSupplier); } /** * Creates new {@code PoolEntry} instance. * * @param route route to the opposite endpoint. * @param timeToLive maximum time to live. May be zero if the connection * does not have an expiry deadline. * @param disposalCallback callback invoked before connection disposal. */ public PoolEntry(final T route, final TimeValue timeToLive, final DisposalCallback disposalCallback) { this(route, timeToLive, disposalCallback, null); } /** * Creates new {@code PoolEntry} instance. * * @param route route to the opposite endpoint. * @param timeToLive maximum time to live. May be zero if the connection * does not have an expiry deadline. */ public PoolEntry(final T route, final TimeValue timeToLive) { this(route, timeToLive, null, null); } public PoolEntry(final T route) { this(route, null); } long getCurrentTime() { return currentTimeSupplier != null ? currentTimeSupplier.get() : System.currentTimeMillis(); } public T getRoute() { return this.route; } public C getConnection() { return this.connRef.get(); } /** * @since 5.0 */ public Deadline getValidityDeadline() { return this.validityDeadline; } public Object getState() { return this.state; } /** * @since 5.2 */ public long getCreated() { return created; } public long getUpdated() { return this.updated; } public Deadline getExpiryDeadline() { return this.expiryDeadline; } /** * @since 5.0 */ public boolean hasConnection() { return this.connRef.get() != null; } /** * @since 5.0 */ public void assignConnection(final C conn) { Args.notNull(conn, "connection"); if (this.connRef.compareAndSet(null, conn)) { this.created = getCurrentTime(); this.updated = this.created; this.validityDeadline = Deadline.calculate(this.created, this.timeToLive); this.expiryDeadline = this.validityDeadline; this.state = null; } else { throw new IllegalStateException("Connection already assigned"); } } /** * @since 5.0 */ public void discardConnection(final CloseMode closeMode) { final C connection = this.connRef.getAndSet(null); if (connection != null) { this.state = null; this.created = 0; this.updated = 0; this.expiryDeadline = Deadline.MIN_VALUE; this.validityDeadline = Deadline.MIN_VALUE; if (this.disposalCallback != null) { this.disposalCallback.execute(connection, closeMode); } else { connection.close(closeMode); } } } /** * @since 5.0 */ public void updateExpiry(final TimeValue expiryTime) { Args.notNull(expiryTime, "Expiry time"); final long currentTime = getCurrentTime(); final Deadline newExpiry = Deadline.calculate(currentTime, expiryTime); this.expiryDeadline = newExpiry.min(this.validityDeadline); this.updated = currentTime; } /** * @since 5.0 */ public void updateState(final Object state) { this.state = state; this.updated = getCurrentTime(); } @Override public String toString() { final StringBuilder buffer = new StringBuilder(); buffer.append("[route:"); buffer.append(this.route); buffer.append("][state:"); buffer.append(this.state); buffer.append("]"); return buffer.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/pool/PoolReusePolicy.java0100644 0000000 0000000 00000003050 00000250600 024462 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; /** * Enumeration of pooled connection re-use policies * * @since 5.0 */ public enum PoolReusePolicy { /** * Re-use as few connections as possible making it possible for connections to become idle and expire. */ LIFO, /** * Re-use all connections equally preventing them from becoming idle and expiring. */ FIFO } httpcore5/src/main/java/org/apache/hc/core5/pool/PoolStats.java0100644 0000000 0000000 00000007335 00000250600 023327 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; import java.io.Serializable; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; /** * Pool statistics. *

* The total number of connections in the pool is equal to {@code available} plus {@code leased}. *

* * @since 4.2 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class PoolStats implements Serializable { private static final long serialVersionUID = -2807686144795228544L; private final int leased; private final int pending; private final int available; private final int max; public PoolStats(final int leased, final int pending, final int free, final int max) { super(); this.leased = leased; this.pending = pending; this.available = free; this.max = max; } /** * Gets the number of persistent connections tracked by the connection manager currently being used to execute * requests. *

* The total number of connections in the pool is equal to {@code available} plus {@code leased}. *

* * @return the number of persistent connections. */ public int getLeased() { return this.leased; } /** * Gets the number of connection requests being blocked awaiting a free connection. This can happen only if there * are more worker threads contending for fewer connections. * * @return the number of connection requests being blocked awaiting a free connection. */ public int getPending() { return this.pending; } /** * Gets the number idle persistent connections. *

* The total number of connections in the pool is equal to {@code available} plus {@code leased}. *

* * @return number idle persistent connections. */ public int getAvailable() { return this.available; } /** * Gets the maximum number of allowed persistent connections. * * @return the maximum number of allowed persistent connections. */ public int getMax() { return this.max; } @Override public String toString() { final StringBuilder buffer = new StringBuilder(); buffer.append("[leased: "); buffer.append(this.leased); buffer.append("; pending: "); buffer.append(this.pending); buffer.append("; available: "); buffer.append(this.available); buffer.append("; max: "); buffer.append(this.max); buffer.append("]"); return buffer.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/pool/RouteSegmentedConnPool.java0100644 0000000 0000000 00000061715 00000250600 026003 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; import java.io.IOException; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.ModalCloseable; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; /** * Lock-free, route-segmented connection pool with tiny, conditional round-robin assistance. * *

Per-route state is kept in independent segments. Disposal of connections is offloaded * to a bounded executor so slow closes do not block threads leasing on other routes. * A minimal round-robin drainer is engaged only when there are many pending routes and * there is global headroom; it never scans all routes.

* * @param route key type * @param connection type (must be {@link ModalCloseable}) * @see ManagedConnPool * @see PoolReusePolicy * @see DisposalCallback * @since 5.4 */ @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL) @Experimental public final class RouteSegmentedConnPool implements ManagedConnPool { // Tiny RR assist: only engage when there are many distinct routes waiting and there is headroom. private static final int RR_MIN_PENDING_ROUTES = 12; private static final int RR_BUDGET = 64; private final PoolReusePolicy reusePolicy; private final TimeValue timeToLive; private final DisposalCallback disposal; private final AtomicInteger defaultMaxPerRoute = new AtomicInteger(5); private final ConcurrentHashMap segments = new ConcurrentHashMap<>(); private final ConcurrentHashMap maxPerRoute = new ConcurrentHashMap<>(); private final AtomicInteger totalAllocated = new AtomicInteger(0); private final AtomicInteger maxTotal = new AtomicInteger(25); private final AtomicBoolean closed = new AtomicBoolean(false); private final ScheduledExecutorService timeouts; /** * Dedicated executor for asynchronous, best-effort disposal. * Bounded queue; on saturation we fall back to IMMEDIATE close on the caller thread. */ private final ThreadPoolExecutor disposer; // Minimal fair round-robin over routes with waiters (no global scans). private final ConcurrentLinkedQueue pendingQueue = new ConcurrentLinkedQueue<>(); private final AtomicBoolean draining = new AtomicBoolean(false); private final AtomicInteger pendingRouteCount = new AtomicInteger(0); public RouteSegmentedConnPool( final int defaultMaxPerRoute, final int maxTotal, final TimeValue timeToLive, final PoolReusePolicy reusePolicy, final DisposalCallback disposal) { this.defaultMaxPerRoute.set(defaultMaxPerRoute > 0 ? defaultMaxPerRoute : 5); this.maxTotal.set(maxTotal > 0 ? maxTotal : 25); this.timeToLive = timeToLive != null ? timeToLive : TimeValue.NEG_ONE_MILLISECOND; this.reusePolicy = reusePolicy != null ? reusePolicy : PoolReusePolicy.LIFO; this.disposal = Args.notNull(disposal, "disposal"); final ThreadFactory tf = r -> { final Thread t = new Thread(r, "seg-pool-timeouts"); t.setDaemon(true); return t; }; this.timeouts = Executors.newSingleThreadScheduledExecutor(tf); // Asynchronous disposer for slow GRACEFUL closes. final int cores = Math.max(2, Runtime.getRuntime().availableProcessors()); final int nThreads = Math.min(8, Math.max(2, cores)); // allow up to 8 on bigger boxes final int qsize = 1024; final ThreadFactory df = r -> { final Thread t = new Thread(r, "seg-pool-disposer"); t.setDaemon(true); return t; }; this.disposer = new ThreadPoolExecutor( nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(qsize), df, new ThreadPoolExecutor.AbortPolicy()); // but we preflight capacity to avoid exception storms } final class Segment { final ConcurrentLinkedDeque> available = new ConcurrentLinkedDeque<>(); final ConcurrentLinkedDeque waiters = new ConcurrentLinkedDeque<>(); final AtomicInteger allocated = new AtomicInteger(0); final AtomicBoolean enqueued = new AtomicBoolean(false); int limitPerRoute(final R route) { final Integer v = maxPerRoute.get(route); return v != null ? v : defaultMaxPerRoute.get(); } } final class Waiter extends CompletableFuture> { final R route; final Timeout requestTimeout; final Object state; volatile boolean cancelled; volatile ScheduledFuture timeoutTask; Waiter(final R route, final Timeout t, final Object s) { this.route = route; this.requestTimeout = t != null ? t : Timeout.DISABLED; this.state = s; this.cancelled = false; this.timeoutTask = null; } @Override public PoolEntry get(final long timeout, final TimeUnit unit) throws InterruptedException, java.util.concurrent.ExecutionException, TimeoutException { try { return super.get(timeout, unit); } catch (final TimeoutException ex) { if (cancel(true)) { throw ex; } return super.get(); } } } @Override public Future> lease( final R route, final Object state, final Timeout requestTimeout, final FutureCallback> callback) { ensureOpen(); final Segment seg = segments.computeIfAbsent(route, r -> new Segment()); // 1) Try available PoolEntry hit; for (; ; ) { hit = pollAvailable(seg, state); if (hit == null) { break; } final long now = System.currentTimeMillis(); if (hit.getExpiryDeadline().isBefore(now) || isPastTtl(hit)) { discardAndDecr(hit, CloseMode.GRACEFUL); continue; } break; } if (hit != null) { if (callback != null) { callback.completed(hit); } return CompletableFuture.completedFuture(hit); } // 2) Try to allocate new within caps if (tryAllocateOne(route, seg)) { final PoolEntry entry = new PoolEntry<>(route, timeToLive, disposal); if (callback != null) { callback.completed(entry); } return CompletableFuture.completedFuture(entry); } // 3) Enqueue waiter with timeout final Waiter w = new Waiter(route, requestTimeout, state); seg.waiters.addLast(w); enqueueIfNeeded(route, seg); // Late hit after enqueuing final PoolEntry late = pollAvailable(seg, state); if (late != null) { if (seg.waiters.remove(w)) { cancelTimeout(w); if (callback != null) { callback.completed(late); } w.complete(late); dequeueIfDrained(seg); return w; } else { boolean handedOff = false; for (Waiter other; (other = seg.waiters.pollFirst()) != null; ) { if (!other.cancelled && compatible(other.state, late.getState())) { cancelTimeout(other); handedOff = other.complete(late); if (handedOff) { break; } } } if (!handedOff) { offerAvailable(seg, late); } } } scheduleTimeout(w, seg); if (callback != null) { w.whenComplete((pe, ex) -> { if (ex != null) { callback.failed(ex instanceof Exception ? (Exception) ex : new Exception(ex)); } else { callback.completed(pe); } }); } triggerDrainIfMany(); return w; } @Override public void release(final PoolEntry entry, final boolean reusable) { if (entry == null) { return; } final R route = entry.getRoute(); final Segment seg = segments.get(route); if (seg == null) { // Segment got removed; dispose off-thread and bail. discardEntry(entry, CloseMode.GRACEFUL); return; } final long now = System.currentTimeMillis(); final boolean stillValid = reusable && !isPastTtl(entry) && !entry.getExpiryDeadline().isBefore(now); if (stillValid) { if (!handOffToCompatibleWaiter(entry, seg)) { offerAvailable(seg, entry); enqueueIfNeeded(route, seg); triggerDrainIfMany(); } } else { discardAndDecr(entry, CloseMode.GRACEFUL); } maybeCleanupSegment(route, seg); } @Override public void close() throws IOException { close(CloseMode.GRACEFUL); } @Override public void close(final CloseMode closeMode) { if (!closed.compareAndSet(false, true)) { return; } timeouts.shutdownNow(); for (final Map.Entry e : segments.entrySet()) { final Segment seg = e.getValue(); for (final Waiter w : seg.waiters) { w.cancelled = true; cancelTimeout(w); w.completeExceptionally(new TimeoutException("Pool closed")); } seg.waiters.clear(); if (seg.enqueued.getAndSet(false)) { pendingRouteCount.decrementAndGet(); } // discard available for (final PoolEntry p : seg.available) { discardEntry(p, closeMode); } seg.available.clear(); final int alloc = seg.allocated.getAndSet(0); if (alloc != 0) { totalAllocated.addAndGet(-alloc); } } segments.clear(); pendingQueue.clear(); pendingRouteCount.set(0); // Let in-flight graceful closes progress; no blocking here. disposer.shutdown(); } @Override public void closeIdle(final TimeValue idleTime) { final long cutoff = System.currentTimeMillis() - Math.max(0L, idleTime != null ? idleTime.toMilliseconds() : 0L); for (final Map.Entry e : segments.entrySet()) { final R route = e.getKey(); final Segment seg = e.getValue(); int processed = 0; final int cap = 64; for (final Iterator> it = seg.available.iterator(); it.hasNext(); ) { final PoolEntry p = it.next(); if (p.getUpdated() <= cutoff) { it.remove(); discardAndDecr(p, CloseMode.GRACEFUL); if (++processed == cap) { break; } } } maybeCleanupSegment(route, seg); } } @Override public void closeExpired() { final long now = System.currentTimeMillis(); for (final Map.Entry e : segments.entrySet()) { final R route = e.getKey(); final Segment seg = e.getValue(); int processed = 0; final int cap = 64; for (final Iterator> it = seg.available.iterator(); it.hasNext(); ) { final PoolEntry p = it.next(); if (p.getExpiryDeadline().isBefore(now) || isPastTtl(p)) { it.remove(); discardAndDecr(p, CloseMode.GRACEFUL); if (++processed == cap) { break; } } } maybeCleanupSegment(route, seg); } } @Override public Set getRoutes() { final Set out = new HashSet<>(); for (final Map.Entry e : segments.entrySet()) { final Segment s = e.getValue(); if (!s.available.isEmpty() || s.allocated.get() > 0 || !s.waiters.isEmpty()) { out.add(e.getKey()); } } return out; } @Override public int getMaxTotal() { return maxTotal.get(); } @Override public void setMaxTotal(final int max) { maxTotal.set(Math.max(1, max)); } @Override public int getDefaultMaxPerRoute() { return defaultMaxPerRoute.get(); } @Override public void setDefaultMaxPerRoute(final int max) { defaultMaxPerRoute.set(Math.max(1, max)); } @Override public int getMaxPerRoute(final R route) { final Integer v = maxPerRoute.get(route); return v != null ? v : defaultMaxPerRoute.get(); } @Override public void setMaxPerRoute(final R route, final int max) { if (max <= 0) { maxPerRoute.remove(route); } else { maxPerRoute.put(route, max); } } @Override public PoolStats getTotalStats() { int leased = 0, availableCount = 0, pending = 0; for (final Segment seg : segments.values()) { final int alloc = seg.allocated.get(); final int avail = seg.available.size(); leased += Math.max(0, alloc - avail); availableCount += avail; pending += seg.waiters.size(); } return new PoolStats(leased, pending, availableCount, getMaxTotal()); } @Override public PoolStats getStats(final R route) { final Segment seg = segments.get(route); if (seg == null) { return new PoolStats(0, 0, 0, getMaxPerRoute(route)); } final int alloc = seg.allocated.get(); final int avail = seg.available.size(); final int leased = Math.max(0, alloc - avail); final int pending = seg.waiters.size(); return new PoolStats(leased, pending, avail, getMaxPerRoute(route)); } private void ensureOpen() { if (closed.get()) { throw new IllegalStateException("Pool is closed"); } } private boolean isPastTtl(final PoolEntry p) { if (timeToLive == null || timeToLive.getDuration() < 0) { return false; } return System.currentTimeMillis() - p.getCreated() >= timeToLive.toMilliseconds(); } private void scheduleTimeout(final Waiter w, final Segment seg) { if (!TimeValue.isPositive(w.requestTimeout)) { return; } w.timeoutTask = timeouts.schedule(() -> { if (w.isDone()) { return; } w.cancelled = true; seg.waiters.remove(w); w.completeExceptionally(new TimeoutException("Lease timed out")); dequeueIfDrained(seg); maybeCleanupSegment(w.route, seg); final PoolEntry p = pollAvailable(seg, w.state); if (p != null) { // Try to hand off that available entry to some other compatible waiter. if (!handOffToCompatibleWaiter(p, seg)) { offerAvailable(seg, p); } } }, w.requestTimeout.toMilliseconds(), TimeUnit.MILLISECONDS); } private void cancelTimeout(final Waiter w) { final ScheduledFuture t = w.timeoutTask; if (t != null) { t.cancel(false); } } private void offerAvailable(final Segment seg, final PoolEntry p) { if (reusePolicy == PoolReusePolicy.LIFO) { seg.available.addFirst(p); } else { seg.available.addLast(p); } } private PoolEntry pollAvailable(final Segment seg, final Object neededState) { if (neededState == null) { return seg.available.pollFirst(); } for (final Iterator> it = seg.available.iterator(); it.hasNext(); ) { final PoolEntry p = it.next(); if (compatible(neededState, p.getState())) { it.remove(); return p; } } return null; } private boolean compatible(final Object needed, final Object have) { return needed == null || Objects.equals(needed, have); } private boolean handOffToCompatibleWaiter(final PoolEntry entry, final Segment seg) { final Deque skipped = new ArrayDeque<>(); boolean handedOff = false; for (; ; ) { final Waiter w = seg.waiters.pollFirst(); if (w == null) { break; } if (w.cancelled || w.isDone()) { continue; } if (compatible(w.state, entry.getState())) { cancelTimeout(w); handedOff = w.complete(entry); if (handedOff) { dequeueIfDrained(seg); break; } } else { skipped.addLast(w); } } // Restore non-compatible waiters to the head to preserve ordering. while (!skipped.isEmpty()) { seg.waiters.addFirst(skipped.pollLast()); } return handedOff; } private void discardAndDecr(final PoolEntry p, final CloseMode mode) { totalAllocated.decrementAndGet(); final Segment seg = segments.get(p.getRoute()); if (seg != null) { seg.allocated.decrementAndGet(); } discardEntry(p, mode); } private CloseMode orImmediate(final CloseMode m) { return m != null ? m : CloseMode.IMMEDIATE; } private void maybeCleanupSegment(final R route, final Segment seg) { if (seg.allocated.get() == 0 && seg.available.isEmpty() && seg.waiters.isEmpty()) { segments.remove(route, seg); if (seg.enqueued.getAndSet(false)) { pendingRouteCount.decrementAndGet(); } } } private boolean tryAllocateOne(final R route, final Segment seg) { for (; ; ) { final int tot = totalAllocated.get(); if (tot >= maxTotal.get()) { return false; } if (!totalAllocated.compareAndSet(tot, tot + 1)) { continue; } for (; ; ) { final int per = seg.allocated.get(); if (per >= seg.limitPerRoute(route)) { totalAllocated.decrementAndGet(); return false; } if (seg.allocated.compareAndSet(per, per + 1)) { return true; } } } } private void enqueueIfNeeded(final R route, final Segment seg) { if (seg.enqueued.compareAndSet(false, true)) { pendingQueue.offer(route); pendingRouteCount.incrementAndGet(); } } private void dequeueIfDrained(final Segment seg) { if (seg.waiters.isEmpty() && seg.enqueued.getAndSet(false)) { pendingRouteCount.decrementAndGet(); } } private void triggerDrainIfMany() { // Engage RR only if there is global headroom and many distinct routes pending if (pendingRouteCount.get() < RR_MIN_PENDING_ROUTES) { return; } if (totalAllocated.get() >= maxTotal.get()) { return; } if (!draining.compareAndSet(false, true)) { return; } disposer.execute(() -> { try { serveRoundRobin(RR_BUDGET); } finally { draining.set(false); if (pendingRouteCount.get() >= RR_MIN_PENDING_ROUTES && totalAllocated.get() < maxTotal.get() && !pendingQueue.isEmpty()) { triggerDrainIfMany(); } } }); } private void serveRoundRobin(final int budget) { int created = 0; for (; created < budget; ) { final R route = pendingQueue.poll(); if (route == null) { break; } final Segment seg = segments.get(route); if (seg == null) { continue; } if (seg.waiters.isEmpty()) { if (seg.enqueued.getAndSet(false)) { pendingRouteCount.decrementAndGet(); } continue; } if (!tryAllocateOne(route, seg)) { // No headroom or hit per-route cap. Re-queue for later. pendingQueue.offer(route); continue; } final Waiter w = seg.waiters.pollFirst(); if (w == null || w.cancelled) { seg.allocated.decrementAndGet(); totalAllocated.decrementAndGet(); } else { final PoolEntry entry = new PoolEntry<>(route, timeToLive, disposal); cancelTimeout(w); w.complete(entry); created++; } if (!seg.waiters.isEmpty()) { pendingQueue.offer(route); } else { if (seg.enqueued.getAndSet(false)) { pendingRouteCount.decrementAndGet(); } } } } /** * Dispose a pool entry's connection asynchronously if possible; under pressure fall back to IMMEDIATE on caller. */ private void discardEntry(final PoolEntry p, final CloseMode preferred) { final CloseMode mode = orImmediate(preferred); // Pre-flight capacity to avoid exception storms under saturation if (disposer.isShutdown()) { p.discardConnection(CloseMode.IMMEDIATE); return; } final LinkedBlockingQueue q = (LinkedBlockingQueue) disposer.getQueue(); if (q.remainingCapacity() == 0) { p.discardConnection(CloseMode.IMMEDIATE); return; } try { disposer.execute(() -> { try { p.discardConnection(mode); } catch (final RuntimeException ignore) { // best-effort } }); } catch (final RejectedExecutionException saturated) { // Saturated or shutting down: never block caller p.discardConnection(CloseMode.IMMEDIATE); } } } httpcore5/src/main/java/org/apache/hc/core5/pool/StrictConnPool.java0100644 0000000 0000000 00000071233 00000250600 024315 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.ListIterator; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; 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.locks.ReentrantLock; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.concurrent.BasicFuture; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.ModalCloseable; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; import org.apache.hc.core5.util.Deadline; import org.apache.hc.core5.util.DeadlineTimeoutException; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; /** * Connection pool with strict connection limit guarantees. * * @param route * @param connection object * * @since 4.2 */ @Contract(threading = ThreadingBehavior.SAFE) public class StrictConnPool implements ManagedConnPool { private final TimeValue timeToLive; private final PoolReusePolicy policy; private final DisposalCallback disposalCallback; private final ConnPoolListener connPoolListener; private final Map> routeToPool; private final LinkedList> pendingRequests; private final Set> leased; private final LinkedList> available; private final ConcurrentLinkedQueue> completedRequests; private final Map maxPerRoute; private final ReentrantLock lock; private final AtomicBoolean isShutDown; private volatile int defaultMaxPerRoute; private volatile int maxTotal; /** * @since 5.0 */ public StrictConnPool( final int defaultMaxPerRoute, final int maxTotal, final TimeValue timeToLive, final PoolReusePolicy policy, final DisposalCallback disposalCallback, final ConnPoolListener connPoolListener) { super(); Args.positive(defaultMaxPerRoute, "Max per route value"); Args.positive(maxTotal, "Max total value"); this.timeToLive = TimeValue.defaultsToNegativeOneMillisecond(timeToLive); this.policy = policy != null ? policy : PoolReusePolicy.LIFO; this.disposalCallback = disposalCallback; this.connPoolListener = connPoolListener; this.routeToPool = new HashMap<>(); this.pendingRequests = new LinkedList<>(); this.leased = new HashSet<>(); this.available = new LinkedList<>(); this.completedRequests = new ConcurrentLinkedQueue<>(); this.maxPerRoute = new HashMap<>(); this.lock = new ReentrantLock(); this.isShutDown = new AtomicBoolean(); this.defaultMaxPerRoute = defaultMaxPerRoute; this.maxTotal = maxTotal; } /** * @since 5.0 */ public StrictConnPool( final int defaultMaxPerRoute, final int maxTotal, final TimeValue timeToLive, final PoolReusePolicy policy, final ConnPoolListener connPoolListener) { this(defaultMaxPerRoute, maxTotal, timeToLive, policy, null, connPoolListener); } public StrictConnPool(final int defaultMaxPerRoute, final int maxTotal) { this(defaultMaxPerRoute, maxTotal, TimeValue.NEG_ONE_MILLISECOND, PoolReusePolicy.LIFO, null); } public boolean isShutdown() { return this.isShutDown.get(); } @Override public void close(final CloseMode closeMode) { if (this.isShutDown.compareAndSet(false, true)) { fireCallbacks(); this.lock.lock(); try { for (final PerRoutePool pool: this.routeToPool.values()) { pool.shutdown(closeMode); } this.routeToPool.clear(); this.leased.clear(); this.available.clear(); this.pendingRequests.clear(); } finally { this.lock.unlock(); } } } @Override public void close() { close(CloseMode.GRACEFUL); } private PerRoutePool getPool(final T route) { return this.routeToPool.computeIfAbsent(route, r -> new PerRoutePool<>(route, this.disposalCallback, this.policy)); } @Override public Future> lease( final T route, final Object state, final Timeout requestTimeout, final FutureCallback> callback) { Args.notNull(route, "Route"); Args.notNull(requestTimeout, "Request timeout"); Asserts.check(!this.isShutDown.get(), "Connection pool shut down"); final Deadline deadline = Deadline.calculate(requestTimeout); final BasicFuture> future = new BasicFuture>(callback) { @Override public PoolEntry get( final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { try { return super.get(timeout, unit); } catch (final TimeoutException ex) { if (cancel()) { throw ex; } return super.get(); } } }; final boolean acquiredLock; try { if (TimeValue.isPositive(requestTimeout)) { acquiredLock = this.lock.tryLock(requestTimeout.getDuration(), requestTimeout.getTimeUnit()); } else { this.lock.lockInterruptibly(); acquiredLock = true; } } catch (final InterruptedException interruptedException) { Thread.currentThread().interrupt(); future.cancel(); return future; } if (acquiredLock) { try { final LeaseRequest request = new LeaseRequest<>(route, state, requestTimeout, future); final boolean completed = processPendingRequest(request); if (!request.isDone() && !completed) { this.pendingRequests.add(request); } if (request.isDone()) { this.completedRequests.add(request); } } finally { this.lock.unlock(); } fireCallbacks(); } else { future.failed(DeadlineTimeoutException.from(deadline)); } return future; } public Future> lease(final T route, final Object state) { return lease(route, state, Timeout.DISABLED, null); } @Override public void release(final PoolEntry entry, final boolean reusable) { if (entry == null) { return; } if (this.isShutDown.get()) { return; } if (!reusable) { entry.discardConnection(CloseMode.GRACEFUL); } this.lock.lock(); try { if (this.leased.remove(entry)) { if (this.connPoolListener != null) { this.connPoolListener.onRelease(entry.getRoute(), this); } final PerRoutePool pool = getPool(entry.getRoute()); final boolean keepAlive = entry.hasConnection() && reusable; pool.free(entry, keepAlive); if (keepAlive) { switch (policy) { case LIFO: this.available.addFirst(entry); break; case FIFO: this.available.addLast(entry); break; default: throw new IllegalStateException("Unexpected ConnPoolPolicy value: " + policy); } } else { entry.discardConnection(CloseMode.GRACEFUL); } processNextPendingRequest(); } else { throw new IllegalStateException("Pool entry is not present in the set of leased entries"); } } finally { this.lock.unlock(); } fireCallbacks(); } private void processPendingRequests() { final ListIterator> it = this.pendingRequests.listIterator(); while (it.hasNext()) { final LeaseRequest request = it.next(); final BasicFuture> future = request.getFuture(); if (future.isCancelled()) { it.remove(); continue; } final boolean completed = processPendingRequest(request); if (request.isDone() || completed) { it.remove(); } if (request.isDone()) { this.completedRequests.add(request); } } } private void processNextPendingRequest() { final ListIterator> it = this.pendingRequests.listIterator(); while (it.hasNext()) { final LeaseRequest request = it.next(); final BasicFuture> future = request.getFuture(); if (future.isCancelled()) { it.remove(); continue; } final boolean completed = processPendingRequest(request); if (request.isDone() || completed) { it.remove(); } if (request.isDone()) { this.completedRequests.add(request); } if (completed) { return; } } } private boolean processPendingRequest(final LeaseRequest request) { final T route = request.getRoute(); final Object state = request.getState(); final Deadline deadline = request.getDeadline(); if (deadline.isExpired()) { request.failed(DeadlineTimeoutException.from(deadline)); return false; } final PerRoutePool pool = getPool(route); PoolEntry entry; for (;;) { entry = pool.getFree(state); if (entry == null) { break; } if (entry.getExpiryDeadline().isExpired()) { entry.discardConnection(CloseMode.GRACEFUL); this.available.remove(entry); pool.free(entry, false); } else { break; } } if (entry != null) { this.available.remove(entry); this.leased.add(entry); request.completed(entry); if (this.connPoolListener != null) { this.connPoolListener.onLease(entry.getRoute(), this); } return true; } // New connection is needed final int maxPerRoute = getMax(route); // Shrink the pool prior to allocating a new connection final int excess = Math.max(0, pool.getAllocatedCount() + 1 - maxPerRoute); if (excess > 0) { for (int i = 0; i < excess; i++) { final PoolEntry lastUsed = pool.getLastUsed(); if (lastUsed == null) { break; } lastUsed.discardConnection(CloseMode.GRACEFUL); this.available.remove(lastUsed); pool.remove(lastUsed); } } if (pool.getAllocatedCount() < maxPerRoute) { final int freeCapacity = Math.max(this.maxTotal - this.leased.size(), 0); if (freeCapacity == 0) { return false; } final int totalAvailable = this.available.size(); if (totalAvailable > freeCapacity - 1) { final PoolEntry lastUsed = this.available.removeLast(); lastUsed.discardConnection(CloseMode.GRACEFUL); final PerRoutePool otherpool = getPool(lastUsed.getRoute()); otherpool.remove(lastUsed); } entry = pool.createEntry(this.timeToLive); this.leased.add(entry); request.completed(entry); if (this.connPoolListener != null) { this.connPoolListener.onLease(entry.getRoute(), this); } return true; } return false; } private void fireCallbacks() { LeaseRequest request; while ((request = this.completedRequests.poll()) != null) { final BasicFuture> future = request.getFuture(); final Exception ex = request.getException(); final PoolEntry result = request.getResult(); boolean successfullyCompleted = false; if (ex != null) { future.failed(ex); } else if (result != null) { if (future.completed(result)) { successfullyCompleted = true; } } else { future.cancel(); } if (!successfullyCompleted) { release(result, true); } } } public void validatePendingRequests() { this.lock.lock(); try { final long now = System.currentTimeMillis(); final ListIterator> it = this.pendingRequests.listIterator(); while (it.hasNext()) { final LeaseRequest request = it.next(); final BasicFuture> future = request.getFuture(); if (future.isCancelled() && !request.isDone()) { it.remove(); } else { final Deadline deadline = request.getDeadline(); if (deadline.isBefore(now)) { request.failed(DeadlineTimeoutException.from(deadline)); } if (request.isDone()) { it.remove(); this.completedRequests.add(request); } } } } finally { this.lock.unlock(); } fireCallbacks(); } private int getMax(final T route) { final Integer v = this.maxPerRoute.get(route); if (v != null) { return v; } return this.defaultMaxPerRoute; } @Override public void setMaxTotal(final int max) { Args.positive(max, "Max value"); this.lock.lock(); try { this.maxTotal = max; } finally { this.lock.unlock(); } } @Override public int getMaxTotal() { this.lock.lock(); try { return this.maxTotal; } finally { this.lock.unlock(); } } @Override public void setDefaultMaxPerRoute(final int max) { Args.positive(max, "Max value"); this.lock.lock(); try { this.defaultMaxPerRoute = max; } finally { this.lock.unlock(); } } @Override public int getDefaultMaxPerRoute() { this.lock.lock(); try { return this.defaultMaxPerRoute; } finally { this.lock.unlock(); } } @Override public void setMaxPerRoute(final T route, final int max) { Args.notNull(route, "Route"); this.lock.lock(); try { if (max > -1) { this.maxPerRoute.put(route, max); } else { this.maxPerRoute.remove(route); } } finally { this.lock.unlock(); } } @Override public int getMaxPerRoute(final T route) { Args.notNull(route, "Route"); this.lock.lock(); try { return getMax(route); } finally { this.lock.unlock(); } } @Override public PoolStats getTotalStats() { this.lock.lock(); try { int pendingCount = 0; for (final LeaseRequest request: pendingRequests) { if (!request.isDone()) { final Deadline deadline = request.getDeadline(); if (!deadline.isExpired()) { pendingCount++; } } } return new PoolStats( this.leased.size(), pendingCount, this.available.size(), this.maxTotal); } finally { this.lock.unlock(); } } @Override public PoolStats getStats(final T route) { Args.notNull(route, "Route"); this.lock.lock(); try { final PerRoutePool pool = getPool(route); int pendingCount = 0; for (final LeaseRequest request: pendingRequests) { if (!request.isDone() && Objects.equals(route, request.getRoute())) { final Deadline deadline = request.getDeadline(); if (!deadline.isExpired()) { pendingCount++; } } } return new PoolStats( pool.getLeasedCount(), pendingCount, pool.getAvailableCount(), getMax(route)); } finally { this.lock.unlock(); } } /** * Returns snapshot of all knows routes * * @since 4.4 */ @Override public Set getRoutes() { this.lock.lock(); try { return new HashSet<>(routeToPool.keySet()); } finally { this.lock.unlock(); } } /** * Enumerates all available connections. * * @since 4.3 */ public void enumAvailable(final Callback> callback) { this.lock.lock(); try { final Iterator> it = this.available.iterator(); while (it.hasNext()) { final PoolEntry entry = it.next(); callback.execute(entry); if (!entry.hasConnection()) { final PerRoutePool pool = getPool(entry.getRoute()); pool.remove(entry); it.remove(); } } processPendingRequests(); purgePoolMap(); } finally { this.lock.unlock(); } } /** * Enumerates all leased connections. * * @since 4.3 */ public void enumLeased(final Callback> callback) { this.lock.lock(); try { final Iterator> it = this.leased.iterator(); while (it.hasNext()) { final PoolEntry entry = it.next(); callback.execute(entry); } processPendingRequests(); } finally { this.lock.unlock(); } } private void purgePoolMap() { final Iterator>> it = this.routeToPool.entrySet().iterator(); while (it.hasNext()) { final Map.Entry> entry = it.next(); final PerRoutePool pool = entry.getValue(); if (pool.getAllocatedCount() == 0) { it.remove(); } } } @Override public void closeIdle(final TimeValue idleTime) { final long deadline = System.currentTimeMillis() - (TimeValue.isPositive(idleTime) ? idleTime.toMilliseconds() : 0); enumAvailable(entry -> { if (entry.getUpdated() <= deadline) { entry.discardConnection(CloseMode.GRACEFUL); } }); } @Override public void closeExpired() { final long now = System.currentTimeMillis(); enumAvailable(entry -> { if (entry.getExpiryDeadline().isBefore(now)) { entry.discardConnection(CloseMode.GRACEFUL); } }); } @Override public String toString() { final StringBuilder buffer = new StringBuilder(); buffer.append("[leased: "); buffer.append(this.leased.size()); buffer.append("][available: "); buffer.append(this.available.size()); buffer.append("][pending: "); buffer.append(this.pendingRequests.size()); buffer.append("]"); return buffer.toString(); } static class LeaseRequest { private final T route; private final Object state; private final Deadline deadline; private final BasicFuture> future; // 'completed' is used internally to guard setting // 'result' and 'ex', but mustn't be used by 'isDone()'. private final AtomicBoolean completed; private volatile PoolEntry result; private volatile Exception ex; /** * Constructor * * @param route route * @param state state * @param requestTimeout timeout to wait in a request queue until kicked off * @param future future callback */ public LeaseRequest( final T route, final Object state, final Timeout requestTimeout, final BasicFuture> future) { super(); this.route = route; this.state = state; this.deadline = Deadline.calculate(requestTimeout); this.future = future; this.completed = new AtomicBoolean(); } public T getRoute() { return this.route; } public Object getState() { return this.state; } public Deadline getDeadline() { return this.deadline; } public boolean isDone() { // This method must not use 'completed.get()' which would result in a race // where a caller may observe completed=true while neither result nor ex // have been set yet. return ex != null || result != null; } public void failed(final Exception ex) { if (this.completed.compareAndSet(false, true)) { this.ex = ex; } } public void completed(final PoolEntry result) { if (this.completed.compareAndSet(false, true)) { this.result = result; } } public BasicFuture> getFuture() { return this.future; } public PoolEntry getResult() { return this.result; } public Exception getException() { return this.ex; } @Override public String toString() { final StringBuilder buffer = new StringBuilder(); buffer.append("["); buffer.append(this.route); buffer.append("]["); buffer.append(this.state); buffer.append("]"); return buffer.toString(); } } static class PerRoutePool { private final T route; private final Set> leased; private final LinkedList> available; private final DisposalCallback disposalCallback; private final PoolReusePolicy policy; PerRoutePool(final T route, final DisposalCallback disposalCallback, final PoolReusePolicy policy) { super(); this.route = route; this.disposalCallback = disposalCallback; this.policy = policy; this.leased = new HashSet<>(); this.available = new LinkedList<>(); } public final T getRoute() { return route; } public int getLeasedCount() { return this.leased.size(); } public int getAvailableCount() { return this.available.size(); } public int getAllocatedCount() { return this.available.size() + this.leased.size(); } public PoolEntry getFree(final Object state) { if (!this.available.isEmpty()) { if (state != null) { final Iterator> it = this.available.iterator(); while (it.hasNext()) { final PoolEntry entry = it.next(); if (state.equals(entry.getState())) { it.remove(); this.leased.add(entry); return entry; } } } final Iterator> it = this.available.iterator(); while (it.hasNext()) { final PoolEntry entry = it.next(); if (entry.getState() == null) { it.remove(); this.leased.add(entry); return entry; } } } return null; } public PoolEntry getLastUsed() { return this.available.peekLast(); } public boolean remove(final PoolEntry entry) { return this.available.remove(entry) || this.leased.remove(entry); } public void free(final PoolEntry entry, final boolean reusable) { final boolean found = this.leased.remove(entry); Asserts.check(found, "Entry %s has not been leased from this pool", entry); if (reusable) { switch (this.policy) { case LIFO: this.available.addFirst(entry); break; case FIFO: this.available.addLast(entry); break; default: throw new IllegalStateException("Unexpected ConnPoolPolicy value: " + policy); } } } public PoolEntry createEntry(final TimeValue timeToLive) { final PoolEntry entry = new PoolEntry<>(this.route, timeToLive, disposalCallback); this.leased.add(entry); return entry; } public void shutdown(final CloseMode closeMode) { PoolEntry availableEntry; while ((availableEntry = available.poll()) != null) { availableEntry.discardConnection(closeMode); } for (final PoolEntry entry: this.leased) { entry.discardConnection(closeMode); } this.leased.clear(); } @Override public String toString() { final StringBuilder buffer = new StringBuilder(); buffer.append("[route: "); buffer.append(this.route); buffer.append("][leased: "); buffer.append(this.leased.size()); buffer.append("][available: "); buffer.append(this.available.size()); buffer.append("]"); return buffer.toString(); } } } httpcore5/src/main/java/org/apache/hc/core5/pool/package-info.java0100644 0000000 0000000 00000002372 00000250600 023717 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Client side connection pool component APIs. */ package org.apache.hc.core5.pool; httpcore5/src/main/java/org/apache/hc/core5/reactor/0040755 0000000 0000000 00000000000 00000250600 021215 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/reactor/AbstractIOReactorBase.java0100644 0000000 0000000 00000005175 00000250600 026173 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.net.SocketAddress; import java.util.concurrent.Future; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; abstract class AbstractIOReactorBase implements ConnectionInitiator, IOReactorService { @Override public final Future connect( final NamedEndpoint remoteEndpoint, final SocketAddress remoteAddress, final SocketAddress localAddress, final Timeout timeout, final Object attachment, final FutureCallback callback) throws IOReactorShutdownException { Args.notNull(remoteEndpoint, "Remote endpoint"); if (getStatus().compareTo(IOReactorStatus.ACTIVE) > 0) { throw new IOReactorShutdownException("I/O reactor has been shut down"); } try { final SingleCoreIOReactor dispatcher = selectWorker(); if (dispatcher.getStatus() == IOReactorStatus.SHUT_DOWN) { throw new IOReactorShutdownException("I/O reactor has been shut down"); } return dispatcher.connect(remoteEndpoint, remoteAddress, localAddress, timeout, attachment, callback); } catch (final IOReactorShutdownException ex) { initiateShutdown(); throw ex; } } abstract SingleCoreIOReactor selectWorker(); } httpcore5/src/main/java/org/apache/hc/core5/reactor/AbstractIOSessionPool.java0100644 0000000 0000000 00000027321 00000250600 026253 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.util.ArrayDeque; import java.util.HashSet; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.concurrent.CompletingFutureContribution; import org.apache.hc.core5.concurrent.ComplexFuture; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.ModalCloseable; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; /** * @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE) public abstract class AbstractIOSessionPool implements ModalCloseable { private final ConcurrentMap sessionPool; private final AtomicBoolean closed; private final ReentrantLock lock; public AbstractIOSessionPool() { super(); this.sessionPool = new ConcurrentHashMap<>(); this.closed = new AtomicBoolean(); this.lock = new ReentrantLock(); } protected abstract Future connectSession( T namedEndpoint, Timeout connectTimeout, FutureCallback callback); protected abstract void validateSession( IOSession ioSession, Callback callback); protected abstract void closeSession( IOSession ioSession, CloseMode closeMode); @Override public final void close(final CloseMode closeMode) { if (closed.compareAndSet(false, true)) { for (final PoolEntry poolEntry : sessionPool.values()) { lock.lock(); try { if (poolEntry.session != null) { closeSession(poolEntry.session, closeMode); poolEntry.session = null; } if (poolEntry.sessionFuture != null) { poolEntry.sessionFuture.cancel(true); poolEntry.sessionFuture = null; } for (;;) { final FutureCallback callback = poolEntry.requestQueue.poll(); if (callback != null) { callback.cancelled(); } else { break; } } } finally { lock.unlock(); } } sessionPool.clear(); } } @Override public final void close() { close(CloseMode.GRACEFUL); } PoolEntry getPoolEntry(final T endpoint) { PoolEntry poolEntry = sessionPool.get(endpoint); if (poolEntry == null) { final PoolEntry newPoolEntry = new PoolEntry(); poolEntry = sessionPool.putIfAbsent(endpoint, newPoolEntry); if (poolEntry == null) { poolEntry = newPoolEntry; } } return poolEntry; } public final Future getSession( final T endpoint, final Timeout connectTimeout, final FutureCallback callback) { Args.notNull(endpoint, "Endpoint"); Asserts.check(!closed.get(), "Connection pool shut down"); final ComplexFuture future = new ComplexFuture<>(callback); final PoolEntry poolEntry = getPoolEntry(endpoint); getSessionInternal(poolEntry, false, endpoint, connectTimeout, new FutureCallback() { @Override public void completed(final IOSession ioSession) { validateSession(ioSession, result -> { if (result) { future.completed(ioSession); } else { getSessionInternal(poolEntry, true, endpoint, connectTimeout, new CompletingFutureContribution<>(future)); } }); } @Override public void failed(final Exception ex) { future.failed(ex); } @Override public void cancelled() { future.cancel(); } }); return future; } private void getSessionInternal( final PoolEntry poolEntry, final boolean requestNew, final T namedEndpoint, final Timeout connectTimeout, final FutureCallback callback) { poolEntry.lock.lock(); try { if (poolEntry.session != null && requestNew) { closeSession(poolEntry.session, CloseMode.GRACEFUL); poolEntry.session = null; } if (poolEntry.session != null && !poolEntry.session.isOpen()) { poolEntry.session = null; } if (poolEntry.session != null) { callback.completed(poolEntry.session); } else { poolEntry.requestQueue.add(callback); if (poolEntry.sessionFuture != null && poolEntry.completed) { poolEntry.sessionFuture = null; } if (poolEntry.sessionFuture == null) { poolEntry.completed = false; poolEntry.sessionFuture = connectSession( namedEndpoint, connectTimeout, new FutureCallback() { @Override public void completed(final IOSession result) { poolEntry.lock.lock(); try { poolEntry.completed = true; if (poolEntry.session == null) { poolEntry.session = result; } else { closeSession(result,CloseMode.GRACEFUL); } for (;;) { final FutureCallback callback = poolEntry.requestQueue.poll(); if (callback != null) { callback.completed(result); } else { break; } } } finally { poolEntry.lock.unlock(); } } @Override public void failed(final Exception ex) { poolEntry.lock.lock(); try { poolEntry.completed = true; poolEntry.session = null; for (;;) { final FutureCallback callback = poolEntry.requestQueue.poll(); if (callback != null) { callback.failed(ex); } else { break; } } } finally { poolEntry.lock.unlock(); } } @Override public void cancelled() { failed(new ConnectionClosedException("Connection request cancelled")); } }); } } } finally { poolEntry.lock.unlock(); } } public final void enumAvailable(final Callback callback) { for (final PoolEntry poolEntry: sessionPool.values()) { if (poolEntry.session != null) { lock.lock(); try { if (poolEntry.session != null) { callback.execute(poolEntry.session); if (!poolEntry.session.isOpen()) { poolEntry.session = null; } } } finally { lock.unlock(); } } } } public final void closeIdle(final TimeValue idleTime) { final long deadline = System.currentTimeMillis() - (TimeValue.isPositive(idleTime) ? idleTime.toMilliseconds() : 0); for (final PoolEntry poolEntry: sessionPool.values()) { if (poolEntry.session != null) { lock.lock(); try { if (poolEntry.session != null && poolEntry.session.getLastReadTime() <= deadline) { closeSession(poolEntry.session, CloseMode.GRACEFUL); poolEntry.session = null; } } finally { lock.unlock(); } } } } public final Set getRoutes() { return new HashSet<>(sessionPool.keySet()); } @Override public String toString() { final StringBuilder buffer = new StringBuilder(); buffer.append("I/O sessions: "); buffer.append(sessionPool.size()); return buffer.toString(); } static class PoolEntry { final Queue> requestQueue; volatile boolean completed; volatile Future sessionFuture; volatile IOSession session; final ReentrantLock lock; // Added PoolEntry() { this.requestQueue = new ArrayDeque<>(); this.lock = new ReentrantLock(); } } } httpcore5/src/main/java/org/apache/hc/core5/reactor/AbstractSingleCoreIOReactor.java0100644 0000000 0000000 00000015363 00000250600 027353 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.io.Closeable; import java.io.IOException; import java.nio.channels.ClosedSelectorException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.Closer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; abstract class AbstractSingleCoreIOReactor implements IOReactor { private final Callback exceptionCallback; private final AtomicReference status; private final AtomicBoolean terminated; private final Condition condition; private final ReentrantLock lock; final Selector selector; AbstractSingleCoreIOReactor(final Callback exceptionCallback) { super(); this.exceptionCallback = exceptionCallback; this.status = new AtomicReference<>(IOReactorStatus.INACTIVE); this.terminated = new AtomicBoolean(); try { this.selector = Selector.open(); } catch (final IOException ex) { throw new IllegalStateException("Unexpected failure opening I/O selector", ex); } this.lock = new ReentrantLock(); this.condition = lock.newCondition(); } @Override public final IOReactorStatus getStatus() { return this.status.get(); } void logException(final Exception ex) { if (exceptionCallback != null) { exceptionCallback.execute(ex); } } abstract void doExecute() throws IOException; abstract void doTerminate() throws IOException; public void execute() { if (this.status.compareAndSet(IOReactorStatus.INACTIVE, IOReactorStatus.ACTIVE)) { try { doExecute(); } catch (final ClosedSelectorException ignore) { // ignore } catch (final Exception ex) { logException(ex); } finally { try { doTerminate(); } catch (final Exception ex) { logException(ex); } finally { close(CloseMode.IMMEDIATE); } } } } @Override public final void awaitShutdown(final TimeValue waitTime) throws InterruptedException { Args.notNull(waitTime, "Wait time"); final long deadline = System.currentTimeMillis() + waitTime.toMilliseconds(); long remaining = waitTime.toMilliseconds(); lock.lock(); try { while (this.status.get().compareTo(IOReactorStatus.SHUT_DOWN) < 0) { condition.await(remaining, TimeUnit.MILLISECONDS); remaining = deadline - System.currentTimeMillis(); if (remaining <= 0) { return; } } } finally { lock.unlock(); } } @Override public final void initiateShutdown() { if (this.status.compareAndSet(IOReactorStatus.INACTIVE, IOReactorStatus.SHUT_DOWN)) { lock.lock(); try { condition.signalAll(); } finally { lock.unlock(); } } else if (this.status.compareAndSet(IOReactorStatus.ACTIVE, IOReactorStatus.SHUTTING_DOWN)) { this.selector.wakeup(); } } @Override public final void close(final CloseMode closeMode) { close(closeMode, Timeout.ofSeconds(5)); } /** * Shuts down the I/O reactor either gracefully or immediately. * During graceful shutdown individual I/O sessions should be * informed about imminent termination and be given a grace period * to complete the ongoing I/O sessions. During immediate shutdown * all ongoing I/O sessions get aborted immediately. * * @param closeMode How to close the IO reactor. * @param timeout How long to wait for the IO reactor to close gracefully. * @since 5.2 */ public void close(final CloseMode closeMode, final Timeout timeout) { if (closeMode == CloseMode.GRACEFUL) { initiateShutdown(); try { awaitShutdown(timeout); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } } this.status.set(IOReactorStatus.SHUT_DOWN); if (terminated.compareAndSet(false, true)) { try { final Set keys = this.selector.keys(); for (final SelectionKey key : keys) { try { Closer.close((Closeable) key.attachment()); } catch (final IOException ex) { logException(ex); } key.channel().close(); } selector.close(); } catch (final Exception ex) { logException(ex); } } lock.lock(); try { condition.signalAll(); } finally { lock.unlock(); } } @Override public final void close() { close(CloseMode.GRACEFUL); } @Override public String toString() { return super.toString() + " [status=" + status + "]"; } } httpcore5/src/main/java/org/apache/hc/core5/reactor/ChannelEntry.java0100644 0000000 0000000 00000003257 00000250600 024456 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.nio.channels.SocketChannel; final class ChannelEntry { final SocketChannel channel; final Object attachment; public ChannelEntry(final SocketChannel channel, final Object attachment) { super(); this.channel = channel; this.attachment = attachment; } @Override public String toString() { return "[" + "channel=" + channel + ", attachment=" + attachment + ']'; } } httpcore5/src/main/java/org/apache/hc/core5/reactor/Command.java0100644 0000000 0000000 00000002753 00000250600 023442 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import org.apache.hc.core5.concurrent.Cancellable; /** * Abstract command {@link IOSession} can act upon. Pending commands * can be cancelled with {@link Cancellable#cancel()}. * * @since 5.0 */ public interface Command extends Cancellable { enum Priority { NORMAL, IMMEDIATE } } httpcore5/src/main/java/org/apache/hc/core5/reactor/ConnectionAcceptor.java0100644 0000000 0000000 00000006304 00000250600 025640 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.io.IOException; import java.net.SocketAddress; import java.util.Set; import java.util.concurrent.Future; import org.apache.hc.core5.concurrent.FutureCallback; /** * Non-blocking connection acceptor. * * @since 5.0 */ public interface ConnectionAcceptor { /** * Opens a new listener endpoint with the given socket address. Once * the endpoint is fully initialized it starts accepting incoming * connections and propagates I/O activity notifications to the I/O event * dispatcher. * * @param address the socket address to listen on. * @param attachment the attachment object. * @param callback the result callback. * @return listener endpoint. * * @since 5.2 */ default Future listen( SocketAddress address, Object attachment, FutureCallback callback) { return listen(address, callback); } /** * Opens a new listener endpoint with the given socket address. Once * the endpoint is fully initialized it starts accepting incoming * connections and propagates I/O activity notifications to the I/O event * dispatcher. * * @param address the socket address to listen on. * @param callback the result callback. * @return listener endpoint. */ Future listen(SocketAddress address, FutureCallback callback); /** * Suspends the I/O reactor preventing it from accepting new connections on * all active endpoints. * * @throws IOException in case of an I/O error. */ void pause() throws IOException; /** * Resumes the I/O reactor restoring its ability to accept incoming * connections on all active endpoints. * * @throws IOException in case of an I/O error. */ void resume() throws IOException; /** * Returns a set of endpoints for this I/O reactor. * * @return set of endpoints. */ Set getEndpoints(); } httpcore5/src/main/java/org/apache/hc/core5/reactor/ConnectionInitiator.java0100644 0000000 0000000 00000006620 00000250600 026043 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.net.SocketAddress; import java.util.concurrent.Future; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.util.Timeout; /** * Non-blocking connection initiator. * * @since 5.0 */ public interface ConnectionInitiator { /** * Requests a connection to a remote host. *

* Opening a connection to a remote host usually tends to be a time * consuming process and may take a while to complete. One can monitor and * control the process of session initialization by means of the * {@link Future} interface. *

* There are several parameters one can use to exert a greater control over * the process of session initialization: *

* A non-null local socket address parameter can be used to bind the socket * to a specific local address. *

* An attachment object can added to the new session's context upon * initialization. This object can be used to pass an initial processing * state to the protocol handler. *

* It is often desirable to be able to react to the completion of a session * request asynchronously without having to wait for it, blocking the * current thread of execution. One can optionally provide an implementation * {@link FutureCallback} instance to get notified of events related * to session requests, such as request completion, cancellation, failure or * timeout. * * @param remoteEndpoint name of the remote host. * @param remoteAddress remote socket address. * @param localAddress local socket address. Can be {@code null}, * in which can the default local address and a random port will be used. * @param timeout connect timeout. * @param attachment the attachment object. Can be {@code null}. * @param callback interface. Can be {@code null}. * @return session request object. */ Future connect( NamedEndpoint remoteEndpoint, SocketAddress remoteAddress, SocketAddress localAddress, Timeout timeout, Object attachment, FutureCallback callback); } httpcore5/src/main/java/org/apache/hc/core5/reactor/DefaultConnectingIOReactor.java0100644 0000000 0000000 00000013565 00000250600 027233 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.io.IOException; import java.util.concurrent.ThreadFactory; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.concurrent.DefaultThreadFactory; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TimeValue; /** * Multi-core I/O reactor that can act as {@link ConnectionInitiator} Internally * this I/O reactor distributes newly created I/O session equally across multiple * I/O worker threads for a more optimal resource utilization and a better * I/O performance. Usually it is recommended to have one worker I/O reactor * per physical CPU core. * * @since 4.0 */ public class DefaultConnectingIOReactor extends AbstractIOReactorBase { private final SingleCoreIOReactor[] workers; private final MultiCoreIOReactor ioReactor; private final IOWorkerSelector workerSelector; private final static ThreadFactory THREAD_FACTORY = new DefaultThreadFactory("I/O client dispatch", true); /** * @since 5.4 */ @Internal public DefaultConnectingIOReactor( final IOEventHandlerFactory eventHandlerFactory, final IOReactorConfig ioReactorConfig, final ThreadFactory threadFactory, final Decorator ioSessionDecorator, final Callback exceptionCallback, final IOSessionListener sessionListener, final IOReactorMetricsListener threadPoolListener, final Callback sessionShutdownCallback, final IOWorkerSelector workerSelector) { Args.notNull(eventHandlerFactory, "Event handler factory"); final int workerCount = ioReactorConfig != null ? ioReactorConfig.getIoThreadCount() : IOReactorConfig.DEFAULT.getIoThreadCount(); this.workers = new SingleCoreIOReactor[workerCount]; final Thread[] threads = new Thread[workerCount]; for (int i = 0; i < this.workers.length; i++) { final SingleCoreIOReactor dispatcher = new SingleCoreIOReactor( exceptionCallback, eventHandlerFactory, ioReactorConfig != null ? ioReactorConfig : IOReactorConfig.DEFAULT, ioSessionDecorator, sessionListener, threadPoolListener, sessionShutdownCallback); this.workers[i] = dispatcher; threads[i] = (threadFactory != null ? threadFactory : THREAD_FACTORY).newThread(new IOReactorWorker(dispatcher)); } this.ioReactor = new MultiCoreIOReactor(this.workers, threads); this.workerSelector = workerSelector != null ? workerSelector : IOWorkerSelectors.newSelector(workerCount); } public DefaultConnectingIOReactor( final IOEventHandlerFactory eventHandlerFactory, final IOReactorConfig ioReactorConfig, final ThreadFactory threadFactory, final Decorator ioSessionDecorator, final Callback exceptionCallback, final IOSessionListener sessionListener, final Callback sessionShutdownCallback) { this(eventHandlerFactory,ioReactorConfig, threadFactory,ioSessionDecorator, exceptionCallback, sessionListener, null, sessionShutdownCallback, null); } public DefaultConnectingIOReactor( final IOEventHandlerFactory eventHandlerFactory, final IOReactorConfig config, final Callback sessionShutdownCallback) { this(eventHandlerFactory, config, null, null, null, null, sessionShutdownCallback); } /** * Creates an instance of DefaultConnectingIOReactor with default configuration. * * @since 5.0 */ public DefaultConnectingIOReactor(final IOEventHandlerFactory eventHandlerFactory) { this(eventHandlerFactory, null, null); } @Override public void start() { ioReactor.start(); } @Override public IOReactorStatus getStatus() { return ioReactor.getStatus(); } @Override SingleCoreIOReactor selectWorker() { return workers[workerSelector.select(workers)]; } @Override public void initiateShutdown() { ioReactor.initiateShutdown(); } @Override public void awaitShutdown(final TimeValue waitTime) throws InterruptedException { ioReactor.awaitShutdown(waitTime); } @Override public void close(final CloseMode closeMode) { ioReactor.close(closeMode); } @Override public void close() throws IOException { ioReactor.close(); } } httpcore5/src/main/java/org/apache/hc/core5/reactor/DefaultListeningIOReactor.java0100644 0000000 0000000 00000021223 00000250600 027066 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.io.IOException; import java.net.SocketAddress; import java.util.Set; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.concurrent.DefaultThreadFactory; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TimeValue; /** * Multi-core I/O reactor that can act as both {@link ConnectionInitiator} * and {@link ConnectionAcceptor}. Internally this I/O reactor distributes newly created * I/O session equally across multiple I/O worker threads for a more optimal resource * utilization and a better I/O performance. Usually it is recommended to have * one worker I/O reactor per physical CPU core. * * @since 4.0 */ public class DefaultListeningIOReactor extends AbstractIOReactorBase implements ConnectionAcceptor { private final static ThreadFactory DISPATCH_THREAD_FACTORY = new DefaultThreadFactory("I/O server dispatch", true); private final static ThreadFactory LISTENER_THREAD_FACTORY = new DefaultThreadFactory("I/O listener", true); private final SingleCoreIOReactor[] workers; private final SingleCoreListeningIOReactor listener; private final MultiCoreIOReactor ioReactor; private final IOWorkerSelector workerSelector; /** * @since 5.4 */ @Internal public DefaultListeningIOReactor( final IOEventHandlerFactory eventHandlerFactory, final IOReactorConfig ioReactorConfig, final ThreadFactory dispatchThreadFactory, final ThreadFactory listenerThreadFactory, final Decorator ioSessionDecorator, final Callback exceptionCallback, final IOSessionListener sessionListener, final IOReactorMetricsListener threadPoolListener, final Callback sessionShutdownCallback, final IOWorkerSelector workerSelector) { Args.notNull(eventHandlerFactory, "Event handler factory"); final int workerCount = ioReactorConfig != null ? ioReactorConfig.getIoThreadCount() : IOReactorConfig.DEFAULT.getIoThreadCount(); this.workers = new SingleCoreIOReactor[workerCount]; final Thread[] threads = new Thread[workerCount + 1]; for (int i = 0; i < this.workers.length; i++) { final SingleCoreIOReactor dispatcher = new SingleCoreIOReactor( exceptionCallback, eventHandlerFactory, ioReactorConfig != null ? ioReactorConfig : IOReactorConfig.DEFAULT, ioSessionDecorator, sessionListener, threadPoolListener, sessionShutdownCallback); this.workers[i] = dispatcher; threads[i + 1] = (dispatchThreadFactory != null ? dispatchThreadFactory : DISPATCH_THREAD_FACTORY).newThread(new IOReactorWorker(dispatcher)); } final IOReactor[] ioReactors = new IOReactor[workerCount + 1]; System.arraycopy(this.workers, 0, ioReactors, 1, workerCount); this.listener = new SingleCoreListeningIOReactor(exceptionCallback, ioReactorConfig, this::enqueueChannel); ioReactors[0] = this.listener; threads[0] = (listenerThreadFactory != null ? listenerThreadFactory : LISTENER_THREAD_FACTORY).newThread(new IOReactorWorker(listener)); this.ioReactor = new MultiCoreIOReactor(ioReactors, threads); this.workerSelector = workerSelector != null ? workerSelector : IOWorkerSelectors.newSelector(workerCount); } /** * Creates an instance of DefaultListeningIOReactor with the given configuration. * * @param eventHandlerFactory the factory to create I/O event handlers. * @param ioReactorConfig I/O reactor configuration. * @param listenerThreadFactory the factory to create listener thread. * Can be {@code null}. * * @since 5.0 */ public DefaultListeningIOReactor( final IOEventHandlerFactory eventHandlerFactory, final IOReactorConfig ioReactorConfig, final ThreadFactory dispatchThreadFactory, final ThreadFactory listenerThreadFactory, final Decorator ioSessionDecorator, final Callback exceptionCallback, final IOSessionListener sessionListener, final Callback sessionShutdownCallback) { this(eventHandlerFactory, ioReactorConfig, dispatchThreadFactory, listenerThreadFactory, ioSessionDecorator, exceptionCallback, sessionListener, null, sessionShutdownCallback, null); } /** * Creates an instance of DefaultListeningIOReactor with the given configuration. * * @param eventHandlerFactory the factory to create I/O event handlers. * @param config I/O reactor configuration. * Can be {@code null}. * * @since 5.0 */ public DefaultListeningIOReactor( final IOEventHandlerFactory eventHandlerFactory, final IOReactorConfig config, final Callback sessionShutdownCallback) { this(eventHandlerFactory, config, null, null, null, null, null, sessionShutdownCallback); } /** * Creates an instance of DefaultListeningIOReactor with default configuration. * * @param eventHandlerFactory the factory to create I/O event handlers. * * @since 5.0 */ public DefaultListeningIOReactor(final IOEventHandlerFactory eventHandlerFactory) { this(eventHandlerFactory, null, null); } @Override public void start() { ioReactor.start(); } @Override public Future listen( final SocketAddress address, final Object attachment, final FutureCallback callback) { return listener.listen(address, attachment, callback); } @Override public Future listen(final SocketAddress address, final FutureCallback callback) { return listen(address, null, callback); } public Future listen(final SocketAddress address) { return listen(address, null); } @Override public Set getEndpoints() { return listener.getEndpoints(); } @Override public void pause() throws IOException { listener.pause(); } @Override public void resume() throws IOException { listener.resume(); } @Override public IOReactorStatus getStatus() { return ioReactor.getStatus(); } @Override SingleCoreIOReactor selectWorker() { return workers[workerSelector.select(workers)]; } private void enqueueChannel(final ChannelEntry entry) { try { selectWorker().enqueueChannel(entry); } catch (final IOReactorShutdownException ex) { initiateShutdown(); } } @Override public void initiateShutdown() { ioReactor.initiateShutdown(); } @Override public void awaitShutdown(final TimeValue waitTime) throws InterruptedException { ioReactor.awaitShutdown(waitTime); } @Override public void close(final CloseMode closeMode) { ioReactor.close(closeMode); } @Override public void close() throws IOException { ioReactor.close(); } } httpcore5/src/main/java/org/apache/hc/core5/reactor/EndpointParameters.java0100644 0000000 0000000 00000005533 00000250600 025667 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.net.Ports; import org.apache.hc.core5.util.Args; /** * Endpoint initialization parameters * * @since 5.1 */ @Internal public final class EndpointParameters implements NamedEndpoint { private final String scheme; private final String hostName; private final int port; private final Object attachment; public EndpointParameters(final String scheme, final String hostName, final int port, final Object attachment) { this.scheme = Args.notBlank(scheme, "Protocol scheme"); this.hostName = Args.notBlank(hostName, "Endpoint name"); this.port = Ports.checkWithDefault(port); this.attachment = attachment; } public EndpointParameters(final HttpHost host, final Object attachment) { Args.notNull(host, "HTTP host"); this.scheme = host.getSchemeName(); this.hostName = host.getHostName(); this.port = host.getPort(); this.attachment = attachment; } public String getScheme() { return scheme; } @Override public String getHostName() { return hostName; } @Override public int getPort() { return port; } public Object getAttachment() { return attachment; } @Override public String toString() { return "EndpointParameters{" + "scheme='" + scheme + '\'' + ", name='" + hostName + '\'' + ", port=" + port + ", attachment=" + attachment + '}'; } } httpcore5/src/main/java/org/apache/hc/core5/reactor/EventMask.java0100644 0000000 0000000 00000003360 00000250600 023754 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.nio.channels.SelectionKey; /** * Type of I/O event notifications I/O sessions can declare interest in. * * @since 4.0 */ public final class EventMask { private EventMask() { // no instances. } /** * Interest in data input. */ public static final int READ = SelectionKey.OP_READ; /** * Interest in data output. */ public static final int WRITE = SelectionKey.OP_WRITE; /** * Interest in data input/output. */ public static final int READ_WRITE = READ | WRITE; } httpcore5/src/main/java/org/apache/hc/core5/reactor/IOEventHandler.java0100644 0000000 0000000 00000006127 00000250600 024672 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.io.IOException; import java.nio.ByteBuffer; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.util.Timeout; /** * IOEventHandler interface is used by I/O reactors to handle I/O events for individual * I/O sessions. All methods of this interface are executed on a single dispatch thread * of the I/O reactor. Therefore, it is important that event processing does not not block * the I/O dispatch thread for too long, thus making the I/O reactor unable to react to * other events. * * @since 5.0 */ @Internal public interface IOEventHandler { /** * Triggered after the given session has been just created. * * @param session the I/O session. * @throws IOException in case of an I/O error. */ void connected(IOSession session) throws IOException; /** * Triggered when the given session has input pending. * * @param session the I/O session. * @throws IOException in case of an I/O error. */ void inputReady(IOSession session, ByteBuffer src) throws IOException; /** * Triggered when the given session is ready for output. * * @param session the I/O session. * @throws IOException in case of an I/O error. */ void outputReady(IOSession session) throws IOException; /** * Triggered when the given session has timed out. * * @param session the I/O session. * @param timeout the timeout. * @throws IOException in case of an I/O error. */ void timeout(IOSession session, Timeout timeout) throws IOException; /** * Triggered when the given session throws a exception. * * @param session the I/O session. */ void exception(IOSession session, Exception cause); /** * Triggered when the given session has been terminated. * * @param session the I/O session. */ void disconnected(IOSession session); } httpcore5/src/main/java/org/apache/hc/core5/reactor/IOEventHandlerFactory.java0100644 0000000 0000000 00000002775 00000250600 026227 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import org.apache.hc.core5.annotation.Internal; /** * Factory interface to create {@link IOEventHandler} instances for the given connected endpoints. * * @since 5.0 */ @Internal public interface IOEventHandlerFactory { IOEventHandler createHandler(ProtocolIOSession ioSession, Object attachment); } httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactor.java0100644 0000000 0000000 00000006565 00000250600 023720 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.ModalCloseable; import org.apache.hc.core5.util.TimeValue; /** * HttpCore NIO is based on the Reactor pattern as described by Doug Lea. * The purpose of I/O reactors is to react to I/O events and to dispatch event * notifications to individual I/O sessions. The main idea of I/O reactor * pattern is to break away from the one thread per connection model imposed * by the classic blocking I/O model. *

* The IOReactor interface represents an abstract object implementing * the Reactor pattern. *

* I/O reactors usually employ a small number of dispatch threads (often as * few as one) to dispatch I/O event notifications to a much greater number * (often as many as several thousands) of I/O sessions or connections. It is * generally recommended to have one dispatch thread per CPU core. * * @since 4.0 */ public interface IOReactor extends ModalCloseable { /** * Shuts down the I/O reactor either gracefully or immediately. * During graceful shutdown individual I/O sessions should be * informed about imminent termination and be given a grace period * to complete the ongoing I/O sessions. During immediate shutdown * all ongoing I/O sessions get aborted immediately. */ @Override void close(CloseMode closeMode); /** * Returns the current status of the reactor. * * @return reactor status. */ IOReactorStatus getStatus(); /** * Initiates shutdown of the reactor without blocking. The reactor is expected * to terminate all active connections, to shut down itself and to release * system resources it currently holds * * @since 5.0 */ void initiateShutdown(); /** * Blocks for the given period of time in milliseconds awaiting * the completion of the reactor shutdown. * * @param waitTime wait time. * @throws InterruptedException Thrown when a thread is waiting, sleeping, or otherwise occupied, * and the thread is interrupted, either before or during the activity. * @since 5.0 */ void awaitShutdown(TimeValue waitTime) throws InterruptedException; } httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactorConfig.java0100644 0000000 0000000 00000046413 00000250600 025042 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.net.SocketAddress; import java.net.SocketOptions; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; /** * I/O reactor configuration parameters. * * @since 4.2 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public final class IOReactorConfig { public static final IOReactorConfig DEFAULT = new Builder().build(); private final TimeValue selectInterval; private final int ioThreadCount; private final Timeout soTimeout; private final boolean soReuseAddress; private final TimeValue soLinger; private final boolean soKeepAlive; private final boolean tcpNoDelay; private final int trafficClass; private final int sndBufSize; private final int rcvBufSize; private final int backlogSize; private final int tcpKeepIdle; private final int tcpKeepInterval; private final int tcpKeepCount; private final SocketAddress socksProxyAddress; private final String socksProxyUsername; private final String socksProxyPassword; IOReactorConfig( final TimeValue selectInterval, final int ioThreadCount, final Timeout soTimeout, final boolean soReuseAddress, final TimeValue soLinger, final boolean soKeepAlive, final boolean tcpNoDelay, final int trafficClass, final int sndBufSize, final int rcvBufSize, final int backlogSize, final int tcpKeepIdle, final int tcpKeepInterval, final int tcpKeepCount, final SocketAddress socksProxyAddress, final String socksProxyUsername, final String socksProxyPassword) { super(); this.selectInterval = selectInterval; this.ioThreadCount = ioThreadCount; this.soTimeout = soTimeout; this.soReuseAddress = soReuseAddress; this.soLinger = soLinger; this.soKeepAlive = soKeepAlive; this.tcpNoDelay = tcpNoDelay; this.trafficClass = trafficClass; this.sndBufSize = sndBufSize; this.rcvBufSize = rcvBufSize; this.backlogSize = backlogSize; this.tcpKeepIdle = tcpKeepIdle; this.tcpKeepInterval = tcpKeepInterval; this.tcpKeepCount = tcpKeepCount; this.socksProxyAddress = socksProxyAddress; this.socksProxyUsername = socksProxyUsername; this.socksProxyPassword = socksProxyPassword; } /** * @see Builder#setSelectInterval(TimeValue) */ public TimeValue getSelectInterval() { return this.selectInterval; } /** * @see Builder#setIoThreadCount(int) */ public int getIoThreadCount() { return this.ioThreadCount; } /** * @see Builder#setSoTimeout(Timeout) */ public Timeout getSoTimeout() { return soTimeout; } /** * @see Builder#setSoReuseAddress(boolean) */ public boolean isSoReuseAddress() { return soReuseAddress; } /** * @see Builder#setSoLinger(TimeValue) */ public TimeValue getSoLinger() { return soLinger; } /** * @see Builder#setSoKeepAlive(boolean) * @since 5.2 */ public boolean isSoKeepAlive() { return this.soKeepAlive; } /** * @see Builder#setSoKeepAlive(boolean) * @deprecated use {@link #isSoKeepAlive()}. */ @Deprecated public boolean isSoKeepalive() { return this.soKeepAlive; } /** * @see Builder#setTcpNoDelay(boolean) */ public boolean isTcpNoDelay() { return tcpNoDelay; } /** * @see Builder#setTrafficClass(int) * * @since 5.1 */ public int getTrafficClass() { return trafficClass; } /** * @see Builder#setSndBufSize(int) */ public int getSndBufSize() { return sndBufSize; } /** * @see Builder#setRcvBufSize(int) */ public int getRcvBufSize() { return rcvBufSize; } /** * @see Builder#setBacklogSize(int) */ public int getBacklogSize() { return backlogSize; } /** * @see Builder#setTcpKeepIdle(int) * * @since 5.3 */ public int getTcpKeepIdle() { return this.tcpKeepIdle; } /** * @see Builder#setTcpKeepInterval(int) * * @since 5.3 */ public int getTcpKeepInterval() { return this.tcpKeepInterval; } /** * @see Builder#setTcpKeepCount(int) * * @since 5.3 */ public int getTcpKeepCount() { return this.tcpKeepCount; } /** * @see Builder#setSocksProxyAddress(SocketAddress) */ public SocketAddress getSocksProxyAddress() { return this.socksProxyAddress; } /** * @see Builder#setSocksProxyUsername(String) */ public String getSocksProxyUsername() { return this.socksProxyUsername; } /** * @see Builder#setSocksProxyAddress(SocketAddress) */ public String getSocksProxyPassword() { return this.socksProxyPassword; } public static Builder custom() { return new Builder(); } public static Builder copy(final IOReactorConfig config) { Args.notNull(config, "I/O reactor config"); return new Builder() .setSelectInterval(config.getSelectInterval()) .setIoThreadCount(config.getIoThreadCount()) .setSoTimeout(config.getSoTimeout()) .setSoReuseAddress(config.isSoReuseAddress()) .setSoLinger(config.getSoLinger()) .setSoKeepAlive(config.isSoKeepAlive()) .setTcpNoDelay(config.isTcpNoDelay()) .setSndBufSize(config.getSndBufSize()) .setRcvBufSize(config.getRcvBufSize()) .setBacklogSize(config.getBacklogSize()) .setTcpKeepIdle(config.getTcpKeepIdle()) .setTcpKeepInterval(config.getTcpKeepInterval()) .setTcpKeepCount(config.getTcpKeepCount()) .setSocksProxyAddress(config.getSocksProxyAddress()) .setSocksProxyUsername(config.getSocksProxyUsername()) .setSocksProxyPassword(config.getSocksProxyPassword()); } public static class Builder { private static int defaultMaxIOThreadCount = -1; /** * Gets the default value for {@code ioThreadCount}. Returns * {@link Runtime#availableProcessors()} if * {@link #setDefaultMaxIOThreadCount(int)} was called with a value less <= 0. * * @return the default value for ioThreadCount. * @since 4.4.10 */ public static int getDefaultMaxIOThreadCount() { return defaultMaxIOThreadCount > 0 ? defaultMaxIOThreadCount : Runtime.getRuntime().availableProcessors(); } /** * Sets the default value for {@code ioThreadCount}. Use a value <= 0 to * cause {@link #getDefaultMaxIOThreadCount()} to return * {@link Runtime#availableProcessors()}. * * @param defaultMaxIOThreadCount * the default value for ioThreadCount. * @since 4.4.10 */ public static void setDefaultMaxIOThreadCount(final int defaultMaxIOThreadCount) { Builder.defaultMaxIOThreadCount = defaultMaxIOThreadCount; } private TimeValue selectInterval; private int ioThreadCount; private Timeout soTimeout; private boolean soReuseAddress; private TimeValue soLinger; private boolean soKeepAlive; private boolean tcpNoDelay; private int trafficClass; private int sndBufSize; private int rcvBufSize; private int backlogSize; private int tcpKeepIdle; private int tcpKeepInterval; private int tcpKeepCount; private SocketAddress socksProxyAddress; private String socksProxyUsername; private String socksProxyPassword; Builder() { this.selectInterval = TimeValue.ofSeconds(1); this.ioThreadCount = Builder.getDefaultMaxIOThreadCount(); this.soTimeout = Timeout.ZERO_MILLISECONDS; this.soReuseAddress = false; this.soLinger = TimeValue.NEG_ONE_SECOND; this.soKeepAlive = true; this.tcpNoDelay = true; this.trafficClass = 0; this.sndBufSize = 0; this.rcvBufSize = 0; this.backlogSize = 0; this.tcpKeepIdle = 5; this.tcpKeepInterval = 5; this.tcpKeepCount = 3; this.socksProxyAddress = null; this.socksProxyUsername = null; this.socksProxyPassword = null; } /** * Sets time interval at which the I/O reactor wakes up to check for timed out sessions * and session requests. *

* Default: {@code 1000} milliseconds. *

* * @return this instance. */ public Builder setSelectInterval(final TimeValue selectInterval) { this.selectInterval = selectInterval; return this; } /** * Sets the number of I/O dispatch threads to be used by the I/O reactor. *

* Default: {@code 2} *

* * @return this instance. */ public Builder setIoThreadCount(final int ioThreadCount) { this.ioThreadCount = ioThreadCount; return this; } /** * Sets the default socket timeout value for non-blocking I/O operations. *

* Default: {@code 0} (no timeout) *

* * @return this instance. * @see SocketOptions#SO_TIMEOUT */ public Builder setSoTimeout(final int soTimeout, final TimeUnit timeUnit) { this.soTimeout = Timeout.of(soTimeout, timeUnit); return this; } /** * Sets the default socket timeout value for non-blocking I/O operations. *

* Default: {@code 0} (no timeout) *

* * @return this instance. * @see SocketOptions#SO_TIMEOUT */ public Builder setSoTimeout(final Timeout soTimeout) { this.soTimeout = soTimeout; return this; } /** * Sets the default value of the {@link SocketOptions#SO_REUSEADDR} parameter * for newly created sockets. *

* Default: {@code false} *

* * @return this instance. * @see SocketOptions#SO_REUSEADDR */ public Builder setSoReuseAddress(final boolean soReuseAddress) { this.soReuseAddress = soReuseAddress; return this; } /** * Sets the default value of the {@link SocketOptions#SO_LINGER} parameter * for newly created sockets. *

* Default: {@code -1} *

* * @return this instance. * @see SocketOptions#SO_LINGER */ public Builder setSoLinger(final int soLinger, final TimeUnit timeUnit) { this.soLinger = TimeValue.of(soLinger, timeUnit); return this; } /** * Sets the default value of the {@link SocketOptions#SO_LINGER} parameter * for newly created sockets. *

* Default: {@code -1} *

* * @return this instance. * @see SocketOptions#SO_LINGER */ public Builder setSoLinger(final TimeValue soLinger) { this.soLinger = soLinger; return this; } /** * Sets the default value of the {@link SocketOptions#SO_KEEPALIVE} parameter * for newly created sockets. *

* Default: {@code true} *

* * @return this instance. * @see SocketOptions#SO_KEEPALIVE */ public Builder setSoKeepAlive(final boolean soKeepAlive) { this.soKeepAlive = soKeepAlive; return this; } /** * Sets the default value of the {@link SocketOptions#TCP_NODELAY} parameter * for newly created sockets. *

* Default: {@code false} *

* * @return this instance. * @see SocketOptions#TCP_NODELAY */ public Builder setTcpNoDelay(final boolean tcpNoDelay) { this.tcpNoDelay = tcpNoDelay; return this; } /** * Sets the default value of the {@link SocketOptions#IP_TOS} parameter * for newly created sockets. *

* Default: {@code 0} *

* * @return this instance. * @see SocketOptions#IP_TOS * * @since 5.1 */ public Builder setTrafficClass(final int trafficClass) { this.trafficClass = trafficClass; return this; } /** * Sets the default value of the {@link SocketOptions#SO_SNDBUF} parameter * for newly created sockets. *

* Default: {@code 0} (system default) *

* * @return this instance. * @see SocketOptions#SO_SNDBUF */ public Builder setSndBufSize(final int sndBufSize) { this.sndBufSize = sndBufSize; return this; } /** * Sets the default value of the {@link SocketOptions#SO_RCVBUF} parameter * for newly created sockets. *

* Default: {@code 0} (system default) *

* * @return this instance. * @see SocketOptions#SO_RCVBUF */ public Builder setRcvBufSize(final int rcvBufSize) { this.rcvBufSize = rcvBufSize; return this; } /** * Sets the default backlog size value for server sockets binds. *

* Default: {@code 0} (system default) *

* * @return this instance. * @since 4.4 */ public Builder setBacklogSize(final int backlogSize) { this.backlogSize = backlogSize; return this; } /** * Sets the time (in seconds) the connection needs to remain idle before TCP starts * sending keepalive probes. *

* Default: {@code 5} *

* * @return this instance. * @since 5.3 */ public Builder setTcpKeepIdle(final int tcpKeepIdle) { this.tcpKeepIdle = tcpKeepIdle; return this; } /** * Sets the time (in seconds) between individual keepalive probes. *

* Default: {@code 5} *

* * @return this instance. * @since 5.3 */ public Builder setTcpKeepInterval(final int tcpKeepInterval) { this.tcpKeepInterval = tcpKeepInterval; return this; } /** * Sets the maximum number of keepalive probes TCP should send before dropping the connection. *

* Default: {@code 3} *

* * @return this instance. * @since 5.3 */ public Builder setTcpKeepCount(final int tcpKeepCount) { this.tcpKeepCount = tcpKeepCount; return this; } /** * Sets the address of the SOCKS proxy to use. * * @return this instance. */ public Builder setSocksProxyAddress(final SocketAddress socksProxyAddress) { this.socksProxyAddress = socksProxyAddress; return this; } /** * Sets the username to provide to the SOCKS proxy for username/password authentication. * * @return this instance. */ public Builder setSocksProxyUsername(final String socksProxyUsername) { this.socksProxyUsername = socksProxyUsername; return this; } /** * Sets the password to provide to the SOCKS proxy for username/password authentication. * * @return this instance. */ public Builder setSocksProxyPassword(final String socksProxyPassword) { this.socksProxyPassword = socksProxyPassword; return this; } public IOReactorConfig build() { return new IOReactorConfig( selectInterval != null ? selectInterval : TimeValue.ofSeconds(1), ioThreadCount, Timeout.defaultsToInfinite(soTimeout), soReuseAddress, TimeValue.defaultsToNegativeOneMillisecond(soLinger), soKeepAlive, tcpNoDelay, trafficClass, sndBufSize, rcvBufSize, backlogSize, tcpKeepIdle, tcpKeepInterval, tcpKeepCount, socksProxyAddress, socksProxyUsername, socksProxyPassword); } } @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("[selectInterval=").append(this.selectInterval) .append(", ioThreadCount=").append(this.ioThreadCount) .append(", soTimeout=").append(this.soTimeout) .append(", soReuseAddress=").append(this.soReuseAddress) .append(", soLinger=").append(this.soLinger) .append(", soKeepAlive=").append(this.soKeepAlive) .append(", tcpNoDelay=").append(this.tcpNoDelay) .append(", trafficClass=").append(this.trafficClass) .append(", sndBufSize=").append(this.sndBufSize) .append(", rcvBufSize=").append(this.rcvBufSize) .append(", backlogSize=").append(this.backlogSize) .append(", socksProxyAddress=").append(this.socksProxyAddress) .append("]"); return builder.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactorMetricsListener.java0100644 0000000 0000000 00000005271 00000250600 026746 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; /** * A listener interface for receiving metrics related to the I/O reactor's * thread pool performance and status. * *

The implementing class can monitor and act upon important metrics such as * active threads, saturation levels, and resource starvation.

* * @since 5.4 */ public interface IOReactorMetricsListener { /** * Invoked to report the current status of the thread pool, including * active threads and pending connections. * * @param activeThreads The number of active threads handling connections. * @param pendingConnections The number of pending connection requests in the queue. */ void onThreadPoolStatus(int activeThreads, int pendingConnections); /** * Invoked to report the saturation level of the thread pool as a percentage * of the active threads to the maximum allowed connections. * * @param saturationPercentage The percentage indicating thread pool saturation. */ void onThreadPoolSaturation(double saturationPercentage); /** * Invoked when the number of pending connection requests exceeds the * maximum allowed connections, indicating possible resource starvation. */ void onResourceStarvationDetected(); /** * Notifies about the average wait time for connection requests in the queue. * * @param averageWaitTimeMillis average time in milliseconds that connection requests spend in the queue. */ void onQueueWaitTime(long averageWaitTimeMillis); } httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactorService.java0100644 0000000 0000000 00000002602 00000250600 025225 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; /** * {@link IOReactor} running as a service. * * @since 5.0 */ public interface IOReactorService extends IOReactor { /** * Starts I/O reactor. */ void start(); } httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactorShutdownException.java0100644 0000000 0000000 00000003023 00000250600 027315 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; /** * Signals the I/O reactor has been shut down or is in the process of shutting down. * * @since 5.0 */ public class IOReactorShutdownException extends IllegalStateException { private static final long serialVersionUID = 1L; public IOReactorShutdownException(final String message) { super(message); } } httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactorStatus.java0100644 0000000 0000000 00000003167 00000250600 025117 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; /** * IOReactorStatus represents an internal status of an I/O reactor. * * @since 4.0 */ public enum IOReactorStatus { /** * The reactor is inactive / has not been started */ INACTIVE, /** * The reactor is active / processing I/O events. */ ACTIVE, /** * The reactor is shutting down. */ SHUTTING_DOWN, /** * The reactor has shut down. */ SHUT_DOWN } httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactorWorker.java0100644 0000000 0000000 00000003500 00000250600 025074 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; final class IOReactorWorker implements Runnable { private final AbstractSingleCoreIOReactor ioReactor; private volatile Throwable throwable; public IOReactorWorker(final AbstractSingleCoreIOReactor ioReactor) { super(); this.ioReactor = ioReactor; } @Override public void run() { try { this.ioReactor.execute(); } catch (final Error ex) { this.throwable = ex; throw ex; } catch (final Exception ex) { this.throwable = ex; } } public Throwable getThrowable() { return this.throwable; } } httpcore5/src/main/java/org/apache/hc/core5/reactor/IOSession.java0100644 0000000 0000000 00000014271 00000250600 023735 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.net.SocketAddress; import java.nio.channels.ByteChannel; import java.util.concurrent.locks.Lock; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.SocketModalCloseable; import org.apache.hc.core5.util.Identifiable; import org.apache.hc.core5.util.Timeout; /** * IOSession interface represents a sequence of logically related data exchanges * between two end points. *

* The channel associated with implementations of this interface can be used to * read data from and write data to the session. *

* I/O sessions are not bound to an execution thread, therefore one cannot use * the context of the thread to store a session's state. All details about * a particular session must be stored within the session itself, usually * using execution context associated with it. *

* Implementations of this interface are expected to be threading safe. * * @since 4.0 */ @Internal public interface IOSession extends ByteChannel, SocketModalCloseable, Identifiable { /** * This enum represents a set of states I/O session transitions through * during its life-span. */ enum Status { ACTIVE, CLOSING, CLOSED } /** * Returns event handler associated with the session. * * @since 5.0 */ IOEventHandler getHandler(); /** * Upgrades event handler associated with the session. * * @since 5.0 */ void upgrade(IOEventHandler handler); /** * Returns session lock that should be used by I/O event handlers * to synchronize access to the session. * * @since 5.0 */ Lock getLock(); /** * Inserts {@link Command} at the end of the command queue. * * @since 5.0 */ void enqueue(Command command, Command.Priority priority); /** * Tests if there enqueued commands pending execution. * * @since 5.0 */ boolean hasCommands(); /** * Removes first {@link Command} from the command queue if available. * * @since 5.0 */ Command poll(); /** * Returns the underlying I/O channel associated with this session. * * @return the I/O channel. */ ByteChannel channel(); /** * Returns address of the remote peer. * * @return socket address. */ SocketAddress getRemoteAddress(); /** * Returns local address. * * @return socket address. */ SocketAddress getLocalAddress(); /** * Returns mask of I/O evens this session declared interest in. * * @return I/O event mask. */ int getEventMask(); /** * Declares interest in I/O event notifications by setting the event mask * associated with the session * * @param ops new I/O event mask. */ void setEventMask(int ops); /** * Declares interest in a particular I/O event type by updating the event * mask associated with the session. * * @param op I/O event type. */ void setEvent(int op); /** * Clears interest in a particular I/O event type by updating the event * mask associated with the session. * * @param op I/O event type. */ void clearEvent(int op); /** * Terminates the session gracefully and closes the underlying I/O channel. * This method ensures that session termination handshake, such as the one * used by the SSL/TLS protocol, is correctly carried out. */ @Override void close(); /** * Returns status of the session: *

* {@link Status#ACTIVE}: session is active. *

* {@link Status#CLOSING}: session is being closed. *

* {@link Status#CLOSED}: session has been terminated. * * @return session status. */ Status getStatus(); /** * Returns value of the socket timeout in milliseconds. The value of * {@code 0} signifies the session cannot time out. * * @return socket timeout. */ @Override Timeout getSocketTimeout(); /** * Sets value of the socket timeout in milliseconds. The value of * {@code 0} signifies the session cannot time out. *

* Please note this operation may affect the last event time. * * @see #getLastEventTime() * * @param timeout socket timeout. */ @Override void setSocketTimeout(Timeout timeout); /** * Returns timestamp of the last read event. * * @return timestamp. */ long getLastReadTime(); /** * Returns timestamp of the last write event. * * @return timestamp. */ long getLastWriteTime(); /** * Returns timestamp of the last I/O event including socket timeout reset. * * @see #getSocketTimeout() * * @return timestamp. */ long getLastEventTime(); /** * Updates the timestamp of the last read event */ void updateReadTime(); /** * Updates the timestamp of the last write event */ void updateWriteTime(); } httpcore5/src/main/java/org/apache/hc/core5/reactor/IOSessionImpl.java0100644 0000000 0000000 00000022724 00000250600 024561 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.io.IOException; import java.net.SocketAddress; import java.net.StandardSocketOptions; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.util.Deque; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.Closer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; class IOSessionImpl implements IOSession { /** Counts instances created. */ private final static AtomicLong COUNT = new AtomicLong(0); private final SelectionKey key; private final SocketChannel channel; private final Deque commandQueue; private final Lock lock; private final String id; private final AtomicReference handlerRef; private final Callback sessionClosedCallback; private final AtomicReference status; private volatile Timeout socketTimeout; private volatile long lastReadTime; private volatile long lastWriteTime; private volatile long lastEventTime; public IOSessionImpl(final String type, final SelectionKey key, final SocketChannel socketChannel, final Callback sessionClosedCallback) { super(); this.key = Args.notNull(key, "Selection key"); this.channel = Args.notNull(socketChannel, "Socket channel"); this.sessionClosedCallback = sessionClosedCallback; this.commandQueue = new ConcurrentLinkedDeque<>(); this.lock = new ReentrantLock(); this.socketTimeout = Timeout.INFINITE; this.id = String.format(type + "-%010d", COUNT.getAndIncrement()); this.handlerRef = new AtomicReference<>(); this.status = new AtomicReference<>(Status.ACTIVE); final long currentTimeMillis = System.currentTimeMillis(); this.lastReadTime = currentTimeMillis; this.lastWriteTime = currentTimeMillis; this.lastEventTime = currentTimeMillis; } @Override public String getId() { return id; } @Override public IOEventHandler getHandler() { return handlerRef.get(); } @Override public void upgrade(final IOEventHandler handler) { handlerRef.set(handler); } @Override public Lock getLock() { return lock; } @Override public void enqueue(final Command command, final Command.Priority priority) { if (priority == Command.Priority.IMMEDIATE) { commandQueue.addFirst(command); } else { commandQueue.add(command); } if (isOpen()) { setEvent(SelectionKey.OP_WRITE); } else { command.cancel(); } } @Override public boolean hasCommands() { return !commandQueue.isEmpty(); } @Override public Command poll() { return commandQueue.poll(); } @Override public ByteChannel channel() { return this.channel; } @Override public SocketAddress getLocalAddress() { try { return this.channel.getLocalAddress(); } catch (final IOException e) { return null; } } @Override public SocketAddress getRemoteAddress() { try { return channel.getRemoteAddress(); } catch (final IOException e) { return null; } } @Override public int getEventMask() { return this.key.interestOps(); } @Override public void setEventMask(final int newValue) { lock.lock(); try { if (isStatusClosed()) { return; } this.key.interestOps(newValue); } finally { lock.unlock(); } this.key.selector().wakeup(); } @Override public void setEvent(final int op) { lock.lock(); try { if (isStatusClosed()) { return; } this.key.interestOps(this.key.interestOps() | op); } finally { lock.unlock(); } this.key.selector().wakeup(); } @Override public void clearEvent(final int op) { lock.lock(); try { if (isStatusClosed()) { return; } this.key.interestOps(this.key.interestOps() & ~op); } finally { lock.unlock(); } this.key.selector().wakeup(); } @Override public Timeout getSocketTimeout() { return this.socketTimeout; } @Override public void setSocketTimeout(final Timeout timeout) { this.socketTimeout = Timeout.defaultsToInfinite(timeout); this.lastEventTime = System.currentTimeMillis(); } @Override public int read(final ByteBuffer dst) throws IOException { return this.channel.read(dst); } @Override public int write(final ByteBuffer src) throws IOException { return this.channel.write(src); } @Override public void updateReadTime() { lastReadTime = System.currentTimeMillis(); lastEventTime = lastReadTime; } @Override public void updateWriteTime() { lastWriteTime = System.currentTimeMillis(); lastEventTime = lastWriteTime; } @Override public long getLastReadTime() { return lastReadTime; } @Override public long getLastWriteTime() { return lastWriteTime; } @Override public long getLastEventTime() { return lastEventTime; } @Override public Status getStatus() { return this.status.get(); } private boolean isStatusClosed() { return this.status.get() == Status.CLOSED; } @Override public boolean isOpen() { return this.status.get() == Status.ACTIVE && this.channel.isOpen(); } @Override public void close() { close(CloseMode.GRACEFUL); } @Override public void close(final CloseMode closeMode) { if (this.status.compareAndSet(Status.ACTIVE, Status.CLOSED)) { try { if (closeMode == CloseMode.IMMEDIATE) { try { this.channel.setOption(StandardSocketOptions.SO_LINGER, 0); } catch (final UnsupportedOperationException | IOException e) { // Quietly ignore } } else { try { this.channel.shutdownOutput(); } catch (final IOException ignore) { } } lock.lock(); try { this.key.cancel(); this.key.attach(null); } finally { lock.unlock(); } Closer.closeQuietly(this.key.channel()); if (this.key.selector().isOpen()) { this.key.selector().wakeup(); } } finally { if (sessionClosedCallback != null) { sessionClosedCallback.execute(this); } } } } private static void formatOps(final StringBuilder buffer, final int ops) { if ((ops & SelectionKey.OP_READ) > 0) { buffer.append('r'); } if ((ops & SelectionKey.OP_WRITE) > 0) { buffer.append('w'); } if ((ops & SelectionKey.OP_ACCEPT) > 0) { buffer.append('a'); } if ((ops & SelectionKey.OP_CONNECT) > 0) { buffer.append('c'); } } @Override public String toString() { final StringBuilder buffer = new StringBuilder(); buffer.append(id).append("["); buffer.append(this.status); buffer.append("]["); if (this.key.isValid()) { formatOps(buffer, this.key.interestOps()); buffer.append(":"); formatOps(buffer, this.key.readyOps()); } buffer.append("]"); return buffer.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/reactor/IOSessionListener.java0100644 0000000 0000000 00000003221 00000250600 025434 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import org.apache.hc.core5.annotation.Internal; /** * {@link IOSession} event listener. * * @since 5.0 */ @Internal public interface IOSessionListener { void connected(IOSession session); void startTls(IOSession session); void inputReady(IOSession session); void outputReady(IOSession session); void timeout(IOSession session); void exception(IOSession session, Exception ex); void disconnected(IOSession session); } httpcore5/src/main/java/org/apache/hc/core5/reactor/IOSessionRequest.java0100644 0000000 0000000 00000010761 00000250600 025306 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.net.SocketAddress; 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.AtomicReference; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.concurrent.BasicFuture; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.ModalCloseable; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.util.Timeout; final class IOSessionRequest implements Future { final NamedEndpoint remoteEndpoint; final SocketAddress remoteAddress; final SocketAddress localAddress; final Timeout timeout; final Object attachment; final BasicFuture future; private final long enqueueTime; private final AtomicReference closeableRef; public IOSessionRequest( final NamedEndpoint remoteEndpoint, final SocketAddress remoteAddress, final SocketAddress localAddress, final Timeout timeout, final Object attachment, final FutureCallback callback) { super(); this.remoteEndpoint = remoteEndpoint; this.remoteAddress = remoteAddress; this.localAddress = localAddress; this.timeout = timeout; this.attachment = attachment; this.future = new BasicFuture<>(callback); this.closeableRef = new AtomicReference<>(); // Set the time when this request is created this.enqueueTime = System.currentTimeMillis(); } public void completed(final ProtocolIOSession ioSession) { future.completed(ioSession); closeableRef.set(null); } public void failed(final Exception cause) { future.failed(cause); closeableRef.set(null); } public boolean cancel() { final boolean cancelled = future.cancel(); final ModalCloseable closeable = closeableRef.getAndSet(null); if (cancelled && closeable != null) { closeable.close(CloseMode.IMMEDIATE); } return cancelled; } @Override public boolean cancel(final boolean mayInterruptIfRunning) { return cancel(); } @Override public boolean isCancelled() { return future.isCancelled(); } public void assign(final ModalCloseable closeable) { closeableRef.set(closeable); } @Override public boolean isDone() { return future.isDone(); } @Override public IOSession get() throws InterruptedException, ExecutionException { return future.get(); } @Override public IOSession get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return future.get(timeout, unit); } @Override public String toString() { return "[" + "remoteEndpoint=" + remoteEndpoint + ", remoteAddress=" + remoteAddress + ", localAddress=" + localAddress + ", attachment=" + attachment + ']'; } // Getter for enqueueTime @Internal public long getEnqueueTime() { return enqueueTime; } } httpcore5/src/main/java/org/apache/hc/core5/reactor/IOWorkerSelector.java0100644 0000000 0000000 00000002554 00000250600 025265 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import org.apache.hc.core5.annotation.Internal; @Internal @FunctionalInterface public interface IOWorkerSelector { int select(IOWorkerStats[] dispatchers); } httpcore5/src/main/java/org/apache/hc/core5/reactor/IOWorkerSelectors.java0100644 0000000 0000000 00000004650 00000250600 025447 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.util.concurrent.atomic.AtomicInteger; final class IOWorkerSelectors { static IOWorkerSelector newSelector(final int workerCount, final int start) { return isPowerOfTwo(workerCount) ? new PowerOfTwoSelector(start) : new GenericSelector(start); } static IOWorkerSelector newSelector(final int workerCount) { return newSelector(workerCount, 0); } static boolean isPowerOfTwo(final int n) { return (n & -n) == n; } static final class PowerOfTwoSelector implements IOWorkerSelector { private final AtomicInteger idx; PowerOfTwoSelector(final int n) { this.idx = new AtomicInteger(n); } @Override public int select(final IOWorkerStats[] dispatchers) { return idx.getAndIncrement() & (dispatchers.length - 1); } } static final class GenericSelector implements IOWorkerSelector { private final AtomicInteger idx; GenericSelector(final int n) { this.idx = new AtomicInteger(n); } @Override public int select(final IOWorkerStats[] dispatchers) { return (idx.getAndIncrement() & Integer.MAX_VALUE) % dispatchers.length; } } } httpcore5/src/main/java/org/apache/hc/core5/reactor/IOWorkerStats.java0100644 0000000 0000000 00000003150 00000250600 024574 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import org.apache.hc.core5.annotation.Internal; /** * Internal I/O dispatch stats that can be used by {@link IOWorkerSelector} * to select the best suited worker to get new I/O channels. * * @since 5.4 */ @Internal public interface IOWorkerStats { // Relatively expensive int totalChannelCount(); // Relatively expensive int pendingChannelCount(); // Cheap long lastSelectMilli(); } httpcore5/src/main/java/org/apache/hc/core5/reactor/InternalChannel.java0100644 0000000 0000000 00000005351 00000250600 025126 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.io.IOException; import java.nio.channels.CancelledKeyException; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.ModalCloseable; import org.apache.hc.core5.util.Timeout; abstract class InternalChannel implements ModalCloseable { abstract void onIOEvent(final int ops) throws IOException; abstract void onTimeout(Timeout timeout) throws IOException; abstract void onException(final Exception cause); abstract Timeout getTimeout(); abstract long getLastEventTime(); final void handleIOEvent(final int ops) { try { onIOEvent(ops); } catch (final CancelledKeyException ex) { close(CloseMode.GRACEFUL); } catch (final Exception ex) { onException(ex); close(CloseMode.GRACEFUL); } } final boolean checkTimeout(final long currentTimeMillis) { final Timeout timeout = getTimeout(); if (!timeout.isDisabled()) { final long timeoutMillis = timeout.toMilliseconds(); final long deadlineMillis = getLastEventTime() + timeoutMillis; if (currentTimeMillis > deadlineMillis) { try { onTimeout(timeout); } catch (final CancelledKeyException ex) { close(CloseMode.GRACEFUL); } catch (final Exception ex) { onException(ex); close(CloseMode.GRACEFUL); } return false; } } return true; } } httpcore5/src/main/java/org/apache/hc/core5/reactor/InternalConnectChannel.java0100644 0000000 0000000 00000011132 00000250600 026432 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.io.IOException; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.Closer; import org.apache.hc.core5.io.SocketTimeoutExceptionFactory; import org.apache.hc.core5.util.Timeout; final class InternalConnectChannel extends InternalChannel { private final SelectionKey key; private final SocketChannel socketChannel; private final IOSessionRequest sessionRequest; private final InternalDataChannel dataChannel; private final IOEventHandlerFactory eventHandlerFactory; private final IOReactorConfig reactorConfig; private final long creationTimeMillis; InternalConnectChannel( final SelectionKey key, final SocketChannel socketChannel, final IOSessionRequest sessionRequest, final InternalDataChannel dataChannel, final IOEventHandlerFactory eventHandlerFactory, final IOReactorConfig reactorConfig) { super(); this.key = key; this.socketChannel = socketChannel; this.sessionRequest = sessionRequest; this.dataChannel = dataChannel; this.eventHandlerFactory = eventHandlerFactory; this.reactorConfig = reactorConfig; this.creationTimeMillis = System.currentTimeMillis(); } @Override void onIOEvent(final int readyOps) throws IOException { if ((readyOps & SelectionKey.OP_CONNECT) != 0) { if (socketChannel.isConnectionPending()) { socketChannel.finishConnect(); } //check out connectTimeout final long now = System.currentTimeMillis(); if (checkTimeout(now)) { if (reactorConfig.getSocksProxyAddress() == null) { dataChannel.upgrade(eventHandlerFactory.createHandler(dataChannel, sessionRequest.attachment)); key.attach(dataChannel); sessionRequest.completed(dataChannel); dataChannel.handleIOEvent(SelectionKey.OP_CONNECT); } else { final IOEventHandler ioEventHandler = new SocksProxyProtocolHandler( dataChannel, sessionRequest, eventHandlerFactory, reactorConfig); dataChannel.upgrade(ioEventHandler); key.attach(dataChannel); ioEventHandler.connected(dataChannel); } } } } @Override Timeout getTimeout() { return sessionRequest.timeout; } @Override long getLastEventTime() { return creationTimeMillis; } @Override void onTimeout(final Timeout timeout) throws IOException { sessionRequest.failed(SocketTimeoutExceptionFactory.create(timeout)); close(); } @Override void onException(final Exception cause) { sessionRequest.failed(cause); } @Override public void close() throws IOException { try { if (!sessionRequest.isDone()) { sessionRequest.cancel(); } } finally { key.cancel(); socketChannel.close(); } } @Override public void close(final CloseMode closeMode) { key.cancel(); Closer.closeQuietly(socketChannel); } @Override public String toString() { return sessionRequest.toString(); } } httpcore5/src/main/java/org/apache/hc/core5/reactor/InternalDataChannel.java0100644 0000000 0000000 00000033727 00000250600 025730 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.nio.channels.SelectionKey; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import org.apache.hc.core5.concurrent.CallbackContribution; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.reactor.ssl.SSLBufferMode; import org.apache.hc.core5.reactor.ssl.SSLIOSession; import org.apache.hc.core5.reactor.ssl.SSLMode; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; import org.apache.hc.core5.reactor.ssl.TlsDetails; import org.apache.hc.core5.reactor.ssl.TransportSecurityLayer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.util.Timeout; final class InternalDataChannel extends InternalChannel implements ProtocolIOSession { private final IOSession ioSession; private final NamedEndpoint initialEndpoint; private final Decorator ioSessionDecorator; private final IOSessionListener sessionListener; private final AtomicReference tlsSessionRef; private final AtomicReference currentSessionRef; private final AtomicReference eventHandlerRef; private final ConcurrentMap protocolUpgradeHandlerMap; private final AtomicBoolean closed; InternalDataChannel( final IOSession ioSession, final NamedEndpoint initialEndpoint, final Decorator ioSessionDecorator, final IOSessionListener sessionListener) { this.ioSession = ioSession; this.initialEndpoint = initialEndpoint; this.ioSessionDecorator = ioSessionDecorator; this.sessionListener = sessionListener; this.tlsSessionRef = new AtomicReference<>(); this.currentSessionRef = new AtomicReference<>( ioSessionDecorator != null ? ioSessionDecorator.decorate(ioSession) : ioSession); this.eventHandlerRef = new AtomicReference<>(); this.protocolUpgradeHandlerMap = new ConcurrentHashMap<>(); this.closed = new AtomicBoolean(); } @Override public String getId() { return ioSession.getId(); } @Override public NamedEndpoint getInitialEndpoint() { return initialEndpoint; } @Override public IOEventHandler getHandler() { return eventHandlerRef.get(); } @Override public void upgrade(final IOEventHandler handler) { final IOSession currentSession = currentSessionRef.get(); currentSession.upgrade(handler); eventHandlerRef.set(handler); } private IOEventHandler ensureHandler(final IOSession session) { final IOEventHandler handler = session.getHandler(); Asserts.notNull(handler, "IO event handler"); return handler; } @Override void onIOEvent(final int readyOps) throws IOException { if ((readyOps & SelectionKey.OP_CONNECT) != 0) { final IOSession currentSession = currentSessionRef.get(); currentSession.clearEvent(SelectionKey.OP_CONNECT); if (tlsSessionRef.get() == null) { if (sessionListener != null) { sessionListener.connected(currentSession); } final IOEventHandler handler = ensureHandler(currentSession); handler.connected(currentSession); } } if ((readyOps & SelectionKey.OP_READ) != 0) { final IOSession currentSession = currentSessionRef.get(); currentSession.updateReadTime(); if (sessionListener != null) { sessionListener.inputReady(currentSession); } final IOEventHandler handler = ensureHandler(currentSession); handler.inputReady(currentSession, null); } if ((readyOps & SelectionKey.OP_WRITE) != 0 || (ioSession.getEventMask() & SelectionKey.OP_WRITE) != 0) { final IOSession currentSession = currentSessionRef.get(); currentSession.updateWriteTime(); if (sessionListener != null) { sessionListener.outputReady(currentSession); } final IOEventHandler handler = ensureHandler(currentSession); handler.outputReady(currentSession); } } @Override Timeout getTimeout() { final IOSession currentSession = currentSessionRef.get(); return currentSession.getSocketTimeout(); } @Override void onTimeout(final Timeout timeout) throws IOException { final IOSession currentSession = currentSessionRef.get(); if (sessionListener != null) { sessionListener.timeout(currentSession); } final IOEventHandler handler = ensureHandler(currentSession); handler.timeout(currentSession, timeout); } @Override void onException(final Exception cause) { final IOSession currentSession = currentSessionRef.get(); if (sessionListener != null) { sessionListener.exception(currentSession, cause); } final IOEventHandler handler = currentSession.getHandler(); if (handler != null) { handler.exception(currentSession, cause); } } void onTLSSessionStart(final SSLIOSession sslSession) { final IOSession currentSession = currentSessionRef.get(); if (sessionListener != null) { sessionListener.connected(currentSession); } } @Override public void startTls( final SSLContext sslContext, final NamedEndpoint endpoint, final SSLBufferMode sslBufferMode, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier, final Timeout handshakeTimeout) { startTls(sslContext, endpoint, sslBufferMode, initializer, verifier, handshakeTimeout, null); } @Override public void startTls( final SSLContext sslContext, final NamedEndpoint endpoint, final SSLBufferMode sslBufferMode, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier, final Timeout handshakeTimeout, final FutureCallback callback) { final SSLIOSession sslioSession = new SSLIOSession( endpoint != null ? endpoint : initialEndpoint, ioSession, initialEndpoint != null ? SSLMode.CLIENT : SSLMode.SERVER, sslContext, sslBufferMode, initializer, verifier, handshakeTimeout, this::onTLSSessionStart, null, new CallbackContribution(callback) { @Override public void completed(final SSLSession sslSession) { if (callback != null) { callback.completed(InternalDataChannel.this); } } }); if (tlsSessionRef.compareAndSet(null, sslioSession)) { currentSessionRef.set(ioSessionDecorator != null ? ioSessionDecorator.decorate(sslioSession) : sslioSession); } else { throw new IllegalStateException("TLS already activated"); } try { if (sessionListener != null) { sessionListener.startTls(sslioSession); } sslioSession.beginHandshake(this); } catch (final Exception ex) { onException(ex); } } @SuppressWarnings("resource") @Override public TlsDetails getTlsDetails() { final SSLIOSession sslIoSession = tlsSessionRef.get(); return sslIoSession != null ? sslIoSession.getTlsDetails() : null; } @Override public Lock getLock() { return ioSession.getLock(); } @Override public void close() { close(CloseMode.GRACEFUL); } @Override public void close(final CloseMode closeMode) { if (closeMode == CloseMode.IMMEDIATE) { ioSession.close(closeMode); } else { final IOSession currentSession = currentSessionRef.get(); currentSession.close(closeMode); } } @Override public IOSession.Status getStatus() { final IOSession currentSession = currentSessionRef.get(); return currentSession.getStatus(); } @Override public boolean isOpen() { final IOSession currentSession = currentSessionRef.get(); return currentSession.isOpen(); } @Override public void enqueue(final Command command, final Command.Priority priority) { final IOSession currentSession = currentSessionRef.get(); currentSession.enqueue(command, priority); } @Override public boolean hasCommands() { final IOSession currentSession = currentSessionRef.get(); return currentSession.hasCommands(); } @Override public Command poll() { final IOSession currentSession = currentSessionRef.get(); return currentSession.poll(); } @Override public ByteChannel channel() { final IOSession currentSession = currentSessionRef.get(); return currentSession.channel(); } @Override public SocketAddress getRemoteAddress() { return ioSession.getRemoteAddress(); } @Override public SocketAddress getLocalAddress() { return ioSession.getLocalAddress(); } @Override public int getEventMask() { final IOSession currentSession = currentSessionRef.get(); return currentSession.getEventMask(); } @Override public void setEventMask(final int ops) { final IOSession currentSession = currentSessionRef.get(); currentSession.setEventMask(ops); } @Override public void setEvent(final int op) { final IOSession currentSession = currentSessionRef.get(); currentSession.setEvent(op); } @Override public void clearEvent(final int op) { final IOSession currentSession = currentSessionRef.get(); currentSession.clearEvent(op); } @Override public Timeout getSocketTimeout() { return ioSession.getSocketTimeout(); } @Override public void setSocketTimeout(final Timeout timeout) { ioSession.setSocketTimeout(timeout); } @Override public int read(final ByteBuffer dst) throws IOException { final IOSession currentSession = currentSessionRef.get(); return currentSession.read(dst); } @Override public int write(final ByteBuffer src) throws IOException { final IOSession currentSession = currentSessionRef.get(); return currentSession.write(src); } @Override public void updateReadTime() { ioSession.updateReadTime(); } @Override public void updateWriteTime() { ioSession.updateWriteTime(); } @Override public long getLastReadTime() { return ioSession.getLastReadTime(); } @Override public long getLastWriteTime() { return ioSession.getLastWriteTime(); } @Override public long getLastEventTime() { return ioSession.getLastEventTime(); } @Override public void switchProtocol(final String protocolId, final FutureCallback callback) { Args.notEmpty(protocolId, "Application protocol ID"); final ProtocolUpgradeHandler upgradeHandler = protocolUpgradeHandlerMap.get(TextUtils.toLowerCase(protocolId)); if (upgradeHandler != null) { upgradeHandler.upgrade(this, callback); } else { throw new IllegalStateException("Unsupported protocol: " + protocolId); } } @Override public void registerProtocol(final String protocolId, final ProtocolUpgradeHandler upgradeHandler) { Args.notEmpty(protocolId, "Application protocol ID"); Args.notNull(upgradeHandler, "Protocol upgrade handler"); protocolUpgradeHandlerMap.put(TextUtils.toLowerCase(protocolId), upgradeHandler); } @Override public String toString() { final IOSession currentSession = currentSessionRef.get(); return Objects.toString(currentSession != null ? currentSession : ioSession, null); } } httpcore5/src/main/java/org/apache/hc/core5/reactor/ListenerEndpoint.java0100644 0000000 0000000 00000003572 00000250600 025352 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.net.SocketAddress; import org.apache.hc.core5.io.ModalCloseable; /** * ListenerEndpoint interface represents an endpoint used by an I/O reactor * to listen for incoming connection from remote clients. * * @since 4.0 */ public interface ListenerEndpoint extends ModalCloseable { /** * Returns the socket address of this endpoint. * * @return socket address. */ SocketAddress getAddress(); /** * Determines if this endpoint has been closed and is no longer listens * for incoming connections. * * @return {@code true} if the endpoint has been closed, * {@code false} otherwise. */ boolean isClosed(); } httpcore5/src/main/java/org/apache/hc/core5/reactor/ListenerEndpointImpl.java0100644 0000000 0000000 00000004665 00000250600 026200 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.io.IOException; import java.net.SocketAddress; import java.nio.channels.SelectionKey; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.Closer; class ListenerEndpointImpl implements ListenerEndpoint { private final SelectionKey key; final SocketAddress address; final Object attachment; private final AtomicBoolean closed; public ListenerEndpointImpl(final SelectionKey key, final Object attachment, final SocketAddress address) { super(); this.key = key; this.address = address; this.attachment = attachment; this.closed = new AtomicBoolean(); } @Override public SocketAddress getAddress() { return this.address; } @Override public String toString() { return "endpoint: " + address; } @Override public boolean isClosed() { return this.closed.get(); } @Override public void close() throws IOException { if (closed.compareAndSet(false, true)) { key.cancel(); key.channel().close(); } } @Override public void close(final CloseMode closeMode) { Closer.closeQuietly(this); } } httpcore5/src/main/java/org/apache/hc/core5/reactor/ListenerEndpointRequest.java0100644 0000000 0000000 00000004353 00000250600 026721 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.io.Closeable; import java.net.SocketAddress; import org.apache.hc.core5.concurrent.BasicFuture; final class ListenerEndpointRequest implements Closeable { final SocketAddress address; final Object attachment; final BasicFuture future; ListenerEndpointRequest(final SocketAddress address, final Object attachment, final BasicFuture future) { this.address = address; this.attachment = attachment; this.future = future; } public void completed(final ListenerEndpoint endpoint) { if (future != null) { future.completed(endpoint); } } public void failed(final Exception cause) { if (future != null) { future.failed(cause); } } public void cancel() { if (future != null) { future.cancel(); } } public boolean isCancelled() { return future != null && future.isCancelled(); } @Override public void close() { cancel(); } } httpcore5/src/main/java/org/apache/hc/core5/reactor/MultiCoreIOReactor.java0100644 0000000 0000000 00000013436 00000250600 025537 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.Closer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; class MultiCoreIOReactor implements IOReactor { private final IOReactor[] ioReactors; private final Thread[] threads; private final AtomicReference status; private final AtomicBoolean terminated; MultiCoreIOReactor(final IOReactor[] ioReactors, final Thread[] threads) { super(); this.ioReactors = ioReactors.clone(); this.threads = threads.clone(); this.status = new AtomicReference<>(IOReactorStatus.INACTIVE); this.terminated = new AtomicBoolean(); } @Override public IOReactorStatus getStatus() { return this.status.get(); } /** * Activates all worker I/O reactors. *

* The I/O main reactor will start reacting to I/O events and triggering * notification methods. The worker I/O reactor in their turn will start * reacting to I/O events and dispatch I/O event notifications to the * {@link IOEventHandler} associated with the given I/O session. *

*/ public final void start() { if (this.status.compareAndSet(IOReactorStatus.INACTIVE, IOReactorStatus.ACTIVE)) { for (int i = 0; i < this.threads.length; i++) { this.threads[i].start(); } } } @Override public final void initiateShutdown() { if (this.status.compareAndSet(IOReactorStatus.INACTIVE, IOReactorStatus.SHUT_DOWN) || this.status.compareAndSet(IOReactorStatus.ACTIVE, IOReactorStatus.SHUTTING_DOWN)) { for (int i = 0; i < this.ioReactors.length; i++) { final IOReactor ioReactor = this.ioReactors[i]; ioReactor.initiateShutdown(); } } } @Override public final void awaitShutdown(final TimeValue waitTime) throws InterruptedException { Args.notNull(waitTime, "Wait time"); final long deadline = System.currentTimeMillis() + waitTime.toMilliseconds(); long remaining = waitTime.toMilliseconds(); for (int i = 0; i < this.ioReactors.length; i++) { final IOReactor ioReactor = this.ioReactors[i]; if (ioReactor.getStatus().compareTo(IOReactorStatus.SHUT_DOWN) < 0) { ioReactor.awaitShutdown(TimeValue.of(remaining, TimeUnit.MILLISECONDS)); remaining = deadline - System.currentTimeMillis(); if (remaining <= 0) { return; } } } for (int i = 0; i < this.threads.length; i++) { final Thread thread = this.threads[i]; thread.join(remaining); remaining = deadline - System.currentTimeMillis(); if (remaining <= 0) { return; } } } @Override public final void close(final CloseMode closeMode) { close(closeMode, Timeout.ofSeconds(5)); } /** * Shuts down the I/O reactor either gracefully or immediately. * During graceful shutdown individual I/O sessions should be * informed about imminent termination and be given a grace period * to complete the ongoing I/O sessions. During immediate shutdown * all ongoing I/O sessions get aborted immediately. * * @param closeMode How to close the IO reactor. * @param timeout How long to wait for the IO reactor to close gracefully. * @since 5.2 */ public void close(final CloseMode closeMode, final Timeout timeout) { if (closeMode == CloseMode.GRACEFUL) { initiateShutdown(); try { awaitShutdown(timeout); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } } this.status.set(IOReactorStatus.SHUT_DOWN); if (this.terminated.compareAndSet(false, true)) { for (int i = 0; i < this.ioReactors.length; i++) { Closer.close(this.ioReactors[i], CloseMode.IMMEDIATE); } for (int i = 0; i < this.threads.length; i++) { this.threads[i].interrupt(); } } } @Override public final void close() { close(CloseMode.GRACEFUL); } @Override public String toString() { return getClass().getSimpleName() + " [status=" + status + "]"; } } httpcore5/src/main/java/org/apache/hc/core5/reactor/ProtocolIOSession.java0100644 0000000 0000000 00000004610 00000250600 025453 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.reactor.ssl.TransportSecurityLayer; /** * TLS capable, protocol upgradable {@link IOSession}. * * @since 5.0 */ public interface ProtocolIOSession extends IOSession, TransportSecurityLayer { /** * Switches this I/O session to the application protocol with the given ID. * * @param protocolId the application protocol ID * @param callback the result callback * @throws UnsupportedOperationException if application protocol switch * is not supported. */ default void switchProtocol(String protocolId, FutureCallback callback) throws UnsupportedOperationException { throw new UnsupportedOperationException("Protocol switch not supported"); } /** * Registers protocol upgrade handler with the given application protocol ID. * * @param protocolId the application protocol ID * @param upgradeHandler the upgrade handler. * @since 5.2 */ default void registerProtocol(String protocolId, ProtocolUpgradeHandler upgradeHandler) { } NamedEndpoint getInitialEndpoint(); } httpcore5/src/main/java/org/apache/hc/core5/reactor/ProtocolUpgradeHandler.java0100644 0000000 0000000 00000003245 00000250600 026470 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.concurrent.FutureCallback; /** * Application protocol upgrade handler. This routine can be used to upgrade * I/O sessions to a new application protocol. * * @since 5.2 */ @Internal public interface ProtocolUpgradeHandler { /** * Upgrades application protocol of the given I/O session. */ void upgrade(ProtocolIOSession ioSession, FutureCallback callback); } httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreIOReactor.java0100644 0000000 0000000 00000051414 00000250600 025664 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ProtocolFamily; import java.net.Socket; import java.net.SocketAddress; import java.net.StandardProtocolFamily; import java.net.StandardSocketOptions; import java.net.UnknownHostException; import java.nio.channels.CancelledKeyException; import java.nio.channels.ClosedChannelException; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import jdk.net.ExtendedSocketOptions; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.Closer; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.ReflectionUtils; import org.apache.hc.core5.util.Timeout; class SingleCoreIOReactor extends AbstractSingleCoreIOReactor implements ConnectionInitiator, IOWorkerStats { private static final int MAX_CHANNEL_REQUESTS = 10000; private final IOEventHandlerFactory eventHandlerFactory; private final IOReactorConfig reactorConfig; private final Decorator ioSessionDecorator; private final IOSessionListener sessionListener; private final Callback sessionShutdownCallback; private final Queue closedSessions; private final Queue channelQueue; private final Queue requestQueue; private final AtomicBoolean shutdownInitiated; private final long selectTimeoutMillis; private volatile long lastTimeoutCheckMillis; private volatile long lastSelectMillis; private final IOReactorMetricsListener threadPoolListener; // Atomic variables for tracking total wait time and count of processed requests private final AtomicLong totalWaitTime = new AtomicLong(0); private final AtomicInteger processedRequestCount = new AtomicInteger(0); SingleCoreIOReactor( final Callback exceptionCallback, final IOEventHandlerFactory eventHandlerFactory, final IOReactorConfig reactorConfig, final Decorator ioSessionDecorator, final IOSessionListener sessionListener, final IOReactorMetricsListener threadPoolListener, final Callback sessionShutdownCallback) { super(exceptionCallback); this.eventHandlerFactory = Args.notNull(eventHandlerFactory, "Event handler factory"); this.reactorConfig = Args.notNull(reactorConfig, "I/O reactor config"); this.ioSessionDecorator = ioSessionDecorator; this.sessionListener = sessionListener; this.threadPoolListener = threadPoolListener; this.sessionShutdownCallback = sessionShutdownCallback; this.shutdownInitiated = new AtomicBoolean(); this.closedSessions = new ConcurrentLinkedQueue<>(); this.channelQueue = new ConcurrentLinkedQueue<>(); this.requestQueue = new ConcurrentLinkedQueue<>(); this.selectTimeoutMillis = this.reactorConfig.getSelectInterval().toMilliseconds(); } void enqueueChannel(final ChannelEntry entry) throws IOReactorShutdownException { if (getStatus().compareTo(IOReactorStatus.ACTIVE) > 0) { throw new IOReactorShutdownException("I/O reactor has been shut down"); } this.channelQueue.add(entry); this.selector.wakeup(); } @Override void doTerminate() { closePendingChannels(); closePendingConnectionRequests(); closeOpenChannels(); processClosedSessions(); } @Override void doExecute() throws IOException { while (!Thread.currentThread().isInterrupted()) { final int readyCount = this.selector.select(this.selectTimeoutMillis); if (getStatus().compareTo(IOReactorStatus.SHUTTING_DOWN) >= 0) { if (this.shutdownInitiated.compareAndSet(false, true)) { initiateSessionShutdown(); } closePendingChannels(); } if (getStatus() == IOReactorStatus.SHUT_DOWN) { break; } // Process selected I/O events lastSelectMillis = System.currentTimeMillis(); if (readyCount > 0) { processEvents(this.selector.selectedKeys()); } validateActiveChannels(); // Process closed sessions processClosedSessions(); // If active process new channels if (getStatus() == IOReactorStatus.ACTIVE) { processPendingChannels(); processPendingConnectionRequests(); } reportStatusToThreadPoolListener(); // Exit select loop if graceful shutdown has been completed if (getStatus() == IOReactorStatus.SHUTTING_DOWN && this.selector.keys().isEmpty()) { break; } if (getStatus() == IOReactorStatus.SHUT_DOWN) { break; } } } private void initiateSessionShutdown() { if (this.sessionShutdownCallback != null) { final Set keys = this.selector.keys(); for (final SelectionKey key : keys) { final InternalChannel channel = (InternalChannel) key.attachment(); if (channel instanceof InternalDataChannel) { this.sessionShutdownCallback.execute((InternalDataChannel) channel); } } } } private void validateActiveChannels() { final long currentTimeMillis = System.currentTimeMillis(); if ((currentTimeMillis - this.lastTimeoutCheckMillis) >= this.selectTimeoutMillis) { this.lastTimeoutCheckMillis = currentTimeMillis; for (final SelectionKey key : this.selector.keys()) { checkTimeout(key, currentTimeMillis); } } } private void processEvents(final Set selectedKeys) { for (final SelectionKey key : selectedKeys) { final InternalChannel channel = (InternalChannel) key.attachment(); if (channel != null) { try { channel.handleIOEvent(key.readyOps()); } catch (final CancelledKeyException ex) { channel.close(CloseMode.GRACEFUL); } } } selectedKeys.clear(); } private void processPendingChannels() throws IOException { ChannelEntry entry; for (int i = 0; i < MAX_CHANNEL_REQUESTS && (entry = this.channelQueue.poll()) != null; i++) { final SocketChannel socketChannel = entry.channel; final Object attachment = entry.attachment; try { prepareSocket(socketChannel); socketChannel.configureBlocking(false); } catch (final IOException ex) { logException(ex); try { socketChannel.close(); } catch (final IOException ex2) { logException(ex2); } throw ex; } final SelectionKey key; try { key = socketChannel.register(this.selector, SelectionKey.OP_READ); } catch (final ClosedChannelException ex) { return; } final IOSessionImpl ioSession = new IOSessionImpl("a", key, socketChannel, closedSessions::add); final InternalDataChannel dataChannel = new InternalDataChannel( ioSession, null, ioSessionDecorator, sessionListener); dataChannel.setSocketTimeout(this.reactorConfig.getSoTimeout()); dataChannel.upgrade(this.eventHandlerFactory.createHandler(dataChannel, attachment)); key.attach(dataChannel); dataChannel.handleIOEvent(SelectionKey.OP_CONNECT); } } private void processClosedSessions() { for (;;) { final IOSession ioSession = this.closedSessions.poll(); if (ioSession == null) { break; } try { if (sessionListener != null) { sessionListener.disconnected(ioSession); } final IOEventHandler handler = ioSession.getHandler(); if (handler != null) { handler.disconnected(ioSession); } } catch (final CancelledKeyException ex) { // ignore and move on } } } private void checkTimeout(final SelectionKey key, final long nowMillis) { final InternalChannel channel = (InternalChannel) key.attachment(); if (channel != null) { channel.checkTimeout(nowMillis); } } @Override public Future connect( final NamedEndpoint remoteEndpoint, final SocketAddress remoteAddress, final SocketAddress localAddress, final Timeout timeout, final Object attachment, final FutureCallback callback) throws IOReactorShutdownException { Args.notNull(remoteEndpoint, "Remote endpoint"); final IOSessionRequest sessionRequest = new IOSessionRequest( remoteEndpoint, remoteAddress != null ? remoteAddress : new InetSocketAddress(remoteEndpoint.getHostName(), remoteEndpoint.getPort()), localAddress, timeout, attachment, callback); this.requestQueue.add(sessionRequest); this.selector.wakeup(); return sessionRequest; } @SuppressWarnings("Since15") private void prepareSocket(final SocketChannel socketChannel) throws IOException { if (this.reactorConfig.getSndBufSize() > 0) { socketChannel.setOption(StandardSocketOptions.SO_SNDBUF, this.reactorConfig.getSndBufSize()); } if (this.reactorConfig.getRcvBufSize() > 0) { socketChannel.setOption(StandardSocketOptions.SO_RCVBUF, this.reactorConfig.getRcvBufSize()); } final int linger = this.reactorConfig.getSoLinger().toSecondsIntBound(); if (linger >= 0) { socketChannel.setOption(StandardSocketOptions.SO_LINGER, linger); } // None of the options below this point are applicable to Unix domain sockets. if (!socketChannel.supportedOptions().contains(StandardSocketOptions.TCP_NODELAY)) { return; } socketChannel.setOption(StandardSocketOptions.TCP_NODELAY, this.reactorConfig.isTcpNoDelay()); socketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, this.reactorConfig.isSoKeepAlive()); if (this.reactorConfig.getTrafficClass() > 0) { socketChannel.setOption(StandardSocketOptions.IP_TOS, this.reactorConfig.getTrafficClass()); } if (ReflectionUtils.supportsKeepAliveOptions()) { if (this.reactorConfig.getTcpKeepIdle() > 0) { socketChannel.setOption(ExtendedSocketOptions.TCP_KEEPIDLE, this.reactorConfig.getTcpKeepIdle()); } if (this.reactorConfig.getTcpKeepInterval() > 0) { socketChannel.setOption(ExtendedSocketOptions.TCP_KEEPINTERVAL, this.reactorConfig.getTcpKeepInterval()); } if (this.reactorConfig.getTcpKeepCount() > 0) { socketChannel.setOption(ExtendedSocketOptions.TCP_KEEPCOUNT, this.reactorConfig.getTcpKeepCount()); } } } private void validateAddress(final SocketAddress address) throws UnknownHostException { if (address instanceof InetSocketAddress) { final InetSocketAddress endpoint = (InetSocketAddress) address; if (endpoint.isUnresolved()) { throw new UnknownHostException(endpoint.getHostName()); } } } private void processPendingConnectionRequests() { IOSessionRequest sessionRequest; for (int i = 0; i < MAX_CHANNEL_REQUESTS && (sessionRequest = this.requestQueue.poll()) != null; i++) { if (threadPoolListener != null) { // Calculate wait time safely without keeping long-lived state final long waitTimeMillis = System.currentTimeMillis() - sessionRequest.getEnqueueTime(); // Accumulate total wait time and increment count atomically totalWaitTime.addAndGet(waitTimeMillis); processedRequestCount.incrementAndGet(); threadPoolListener.onQueueWaitTime(waitTimeMillis); } if (!sessionRequest.isCancelled()) { final SocketChannel socketChannel; try { socketChannel = openSocketFor(sessionRequest.remoteAddress); } catch (final IOException ex) { sessionRequest.failed(ex); return; } try { processConnectionRequest(socketChannel, sessionRequest); } catch (final IOException | RuntimeException ex) { Closer.closeQuietly(socketChannel); sessionRequest.failed(ex); } } } } private static SocketChannel openSocketFor(final SocketAddress remoteAddress) throws IOException { if (remoteAddress instanceof InetSocketAddress) { return SocketChannel.open(); } try { return (SocketChannel) SocketChannel.class.getMethod("open", ProtocolFamily.class) .invoke(null, StandardProtocolFamily.valueOf("UNIX")); } catch (final ReflectiveOperationException e) { throw new UnsupportedOperationException("UNIX-family socket channels not supported", e); } } private void processConnectionRequest(final SocketChannel socketChannel, final IOSessionRequest sessionRequest) throws IOException { socketChannel.configureBlocking(false); prepareSocket(socketChannel); validateAddress(sessionRequest.localAddress); if (sessionRequest.localAddress != null) { final Socket sock = socketChannel.socket(); sock.setReuseAddress(this.reactorConfig.isSoReuseAddress()); sock.bind(sessionRequest.localAddress); } final SocketAddress socksProxyAddress = reactorConfig.getSocksProxyAddress(); final SocketAddress remoteAddress = socksProxyAddress != null ? socksProxyAddress : sessionRequest.remoteAddress; // Run this under a doPrivileged to support lib users that run under a SecurityManager this allows granting connect permissions // only to this library validateAddress(remoteAddress); final boolean connected = socketChannel.connect(remoteAddress); final SelectionKey key = socketChannel.register(this.selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ); final IOSessionImpl ioSession = new IOSessionImpl("c", key, socketChannel, closedSessions::add); final InternalDataChannel dataChannel = new InternalDataChannel( ioSession, sessionRequest.remoteEndpoint, ioSessionDecorator, sessionListener); dataChannel.setSocketTimeout(reactorConfig.getSoTimeout()); final InternalChannel connectChannel = new InternalConnectChannel( key, socketChannel, sessionRequest, dataChannel, eventHandlerFactory, reactorConfig); if (connected) { connectChannel.handleIOEvent(SelectionKey.OP_CONNECT); } else { key.attach(connectChannel); sessionRequest.assign(connectChannel); } } private void closeOpenChannels() { for (final SelectionKey key : selector.keys()) { final Object attachment = key.attachment(); if (attachment instanceof InternalChannel) { final InternalChannel channel = (InternalChannel) attachment; channel.close(CloseMode.IMMEDIATE); } } } private void closePendingChannels() { ChannelEntry entry; while ((entry = this.channelQueue.poll()) != null) { final SocketChannel socketChannel = entry.channel; try { socketChannel.close(); } catch (final IOException ex) { logException(ex); } } } private void closePendingConnectionRequests() { IOSessionRequest sessionRequest; while ((sessionRequest = this.requestQueue.poll()) != null) { sessionRequest.cancel(); } } /** * Reports the current status of the I/O reactor's thread pool to the * configured metrics listener. * *

This method gathers three key metrics:

*
    *
  • Active Threads: The number of currently active threads * handling I/O sessions.
  • *
  • Pending Connections: The number of connection requests * waiting to be processed.
  • *
  • Saturation Percentage: The ratio of active threads to the * maximum allowed connections (defined by {@code MAX_CHANNEL_REQUESTS}), * expressed as a percentage. It provides insight into how saturated the thread * pool is relative to its maximum capacity. The formula for calculating saturation * is: *
         *     saturationPercentage = (activeThreads / MAX_CHANNEL_REQUESTS) * 100.0
         *     
  • *
*

* If the number of pending connections exceeds {@code MAX_CHANNEL_REQUESTS}, * resource starvation is detected, and an appropriate event is reported. * */ private void reportStatusToThreadPoolListener() { if (threadPoolListener != null) { // Calculate the number of active threads (connections) final int activeThreads = (int) this.selector.keys().stream() .filter(key -> key.isValid() && key.attachment() instanceof InternalChannel) .count(); // Calculate the number of pending connection requests final int pendingConnections = this.requestQueue.size(); // Calculate saturation as a percentage of active connections to max allowed connections final double saturationPercentage = ((double) activeThreads / MAX_CHANNEL_REQUESTS) * 100.0; // Report thread pool status: active sessions and pending connections threadPoolListener.onThreadPoolStatus(activeThreads, pendingConnections); // Report thread pool saturation threadPoolListener.onThreadPoolSaturation(saturationPercentage); // Detect resource starvation if pending connections exceed threshold if (pendingConnections > MAX_CHANNEL_REQUESTS) { threadPoolListener.onResourceStarvationDetected(); } } } @Override public int totalChannelCount() { return selector.keys().size(); } @Override public int pendingChannelCount() { return channelQueue.size() + requestQueue.size(); } @Override public long lastSelectMilli() { return lastSelectMillis; } } httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreListeningIOReactor.java0100644 0000000 0000000 00000021333 00000250600 027536 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.io.IOException; import java.net.BindException; import java.net.ServerSocket; import java.net.SocketAddress; import java.nio.channels.CancelledKeyException; import java.nio.channels.SelectionKey; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.HashSet; import java.util.Iterator; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.hc.core5.concurrent.BasicFuture; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.io.Closer; class SingleCoreListeningIOReactor extends AbstractSingleCoreIOReactor implements ConnectionAcceptor { private final IOReactorConfig reactorConfig; private final Callback callback; private final Queue requestQueue; private final ConcurrentMap endpoints; private final AtomicBoolean paused; private final long selectTimeoutMillis; SingleCoreListeningIOReactor( final Callback exceptionCallback, final IOReactorConfig ioReactorConfig, final Callback callback) { super(exceptionCallback); this.reactorConfig = ioReactorConfig != null ? ioReactorConfig : IOReactorConfig.DEFAULT; this.callback = callback; this.requestQueue = new ConcurrentLinkedQueue<>(); this.endpoints = new ConcurrentHashMap<>(); this.paused = new AtomicBoolean(); this.selectTimeoutMillis = this.reactorConfig.getSelectInterval().toMilliseconds(); } @Override void doTerminate() { ListenerEndpointRequest request; while ((request = this.requestQueue.poll()) != null) { request.cancel(); } } @Override protected final void doExecute() throws IOException { while (!Thread.currentThread().isInterrupted()) { if (getStatus() != IOReactorStatus.ACTIVE) { break; } final int readyCount = this.selector.select(this.selectTimeoutMillis); if (getStatus() != IOReactorStatus.ACTIVE) { break; } processEvents(readyCount); } } private void processEvents(final int readyCount) throws IOException { if (!this.paused.get()) { processSessionRequests(); } if (readyCount > 0) { final Set selectedKeys = this.selector.selectedKeys(); for (final SelectionKey key : selectedKeys) { processEvent(key); } selectedKeys.clear(); } } private void processEvent(final SelectionKey key) throws IOException { try { if (key.isAcceptable()) { final ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); for (;;) { final SocketChannel socketChannel = serverChannel.accept(); if (socketChannel == null) { break; } final ListenerEndpointRequest endpointRequest = (ListenerEndpointRequest) key.attachment(); this.callback.execute(new ChannelEntry(socketChannel, endpointRequest.attachment)); } } } catch (final CancelledKeyException ex) { final ListenerEndpointImpl endpoint = (ListenerEndpointImpl) key.attachment(); this.endpoints.remove(endpoint); key.attach(null); } } @Override public Future listen( final SocketAddress address, final Object attachment, final FutureCallback callback) { if (getStatus().compareTo(IOReactorStatus.SHUTTING_DOWN) >= 0) { throw new IOReactorShutdownException("I/O reactor has been shut down"); } final BasicFuture future = new BasicFuture<>(callback); this.requestQueue.add(new ListenerEndpointRequest(address, attachment, future)); this.selector.wakeup(); return future; } @Override public Future listen(final SocketAddress address, final FutureCallback callback) { return listen(address, null, callback); } private void processSessionRequests() throws IOException { ListenerEndpointRequest request; while ((request = this.requestQueue.poll()) != null) { if (request.isCancelled()) { continue; } final SocketAddress address = request.address; final ServerSocketChannel serverChannel = ServerSocketChannel.open(); try { final ServerSocket socket = serverChannel.socket(); socket.setReuseAddress(this.reactorConfig.isSoReuseAddress()); if (this.reactorConfig.getRcvBufSize() > 0) { socket.setReceiveBufferSize(this.reactorConfig.getRcvBufSize()); } serverChannel.configureBlocking(false); try { socket.bind(address, this.reactorConfig.getBacklogSize()); } catch (final BindException ex) { final BindException detailedEx = new BindException( String.format("Socket bind failure for socket %s, address=%s, BacklogSize=%d: %s", socket, address, this.reactorConfig.getBacklogSize(), ex)); detailedEx.setStackTrace(ex.getStackTrace()); throw detailedEx; } final SelectionKey key = serverChannel.register(this.selector, SelectionKey.OP_ACCEPT); key.attach(request); final ListenerEndpointImpl endpoint = new ListenerEndpointImpl(key, request.attachment, socket.getLocalSocketAddress()); this.endpoints.put(endpoint, Boolean.TRUE); request.completed(endpoint); } catch (final IOException ex) { Closer.closeQuietly(serverChannel); request.failed(ex); } } } @Override public Set getEndpoints() { final Set set = new HashSet<>(); final Iterator it = this.endpoints.keySet().iterator(); while (it.hasNext()) { final ListenerEndpoint endpoint = it.next(); if (!endpoint.isClosed()) { set.add(endpoint); } else { it.remove(); } } return set; } @Override public void pause() throws IOException { if (paused.compareAndSet(false, true)) { final Iterator it = this.endpoints.keySet().iterator(); while (it.hasNext()) { final ListenerEndpointImpl endpoint = it.next(); if (!endpoint.isClosed()) { endpoint.close(); this.requestQueue.add(new ListenerEndpointRequest(endpoint.address, endpoint.attachment, null)); } it.remove(); } } } @Override public void resume() throws IOException { if (paused.compareAndSet(true, false)) { this.selector.wakeup(); } } } httpcore5/src/main/java/org/apache/hc/core5/reactor/SocksProxyProtocolHandler.java0100644 0000000 0000000 00000037653 00000250600 027237 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.io.IOException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.nio.channels.SelectionKey; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.nio.command.CommandSupport; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.SocketTimeoutExceptionFactory; import org.apache.hc.core5.net.InetAddressUtils; import org.apache.hc.core5.util.Timeout; /** * Implements the client side of SOCKS protocol version 5 as per https://tools.ietf.org/html/rfc1928. Supports SOCKS username/password * authentication as per https://tools.ietf.org/html/rfc1929. */ final class SocksProxyProtocolHandler implements IOEventHandler { private static final int MAX_DNS_NAME_LENGTH = 255; private static final int MAX_COMMAND_CONNECT_LENGTH = 6 + MAX_DNS_NAME_LENGTH + 1; private static final byte CLIENT_VERSION = 5; private static final byte NO_AUTHENTICATION_REQUIRED = 0; private static final byte USERNAME_PASSWORD = 2; private static final byte USERNAME_PASSWORD_VERSION = 1; private static final byte SUCCESS = 0; private static final byte COMMAND_CONNECT = 1; private static final byte ATYP_DOMAINNAME = 3; private enum State { SEND_AUTH, RECEIVE_AUTH_METHOD, SEND_USERNAME_PASSWORD, RECEIVE_AUTH, SEND_CONNECT, RECEIVE_RESPONSE_CODE, RECEIVE_ADDRESS_TYPE, RECEIVE_ADDRESS, COMPLETE } private final InternalDataChannel dataChannel; private final IOSessionRequest sessionRequest; private final IOEventHandlerFactory eventHandlerFactory; private final IOReactorConfig reactorConfig; private ByteBuffer buffer = ByteBuffer.allocate(512); private State state = State.SEND_AUTH; SocksProxyProtocolHandler(final InternalDataChannel dataChannel, final IOSessionRequest sessionRequest, final IOEventHandlerFactory eventHandlerFactory, final IOReactorConfig reactorConfig) { this.dataChannel = dataChannel; this.sessionRequest = sessionRequest; this.eventHandlerFactory = eventHandlerFactory; this.reactorConfig = reactorConfig; } @Override public void connected(final IOSession session) throws IOException { this.buffer.put(CLIENT_VERSION); if (this.reactorConfig.getSocksProxyUsername() != null && this.reactorConfig.getSocksProxyPassword() != null) { this.buffer.put((byte) 2); this.buffer.put(NO_AUTHENTICATION_REQUIRED); this.buffer.put(USERNAME_PASSWORD); } else { this.buffer.put((byte) 1); this.buffer.put(NO_AUTHENTICATION_REQUIRED); } this.buffer.flip(); session.setEventMask(SelectionKey.OP_WRITE); } @Override public void outputReady(final IOSession session) throws IOException { switch (this.state) { case SEND_AUTH: if (writeAndPrepareRead(session, 2)) { session.setEventMask(SelectionKey.OP_READ); this.state = State.RECEIVE_AUTH_METHOD; } break; case SEND_USERNAME_PASSWORD: if (writeAndPrepareRead(session, 2)) { session.setEventMask(SelectionKey.OP_READ); this.state = State.RECEIVE_AUTH; } break; case SEND_CONNECT: if (writeAndPrepareRead(session, 2)) { session.setEventMask(SelectionKey.OP_READ); this.state = State.RECEIVE_RESPONSE_CODE; } break; case RECEIVE_AUTH_METHOD: case RECEIVE_AUTH: case RECEIVE_ADDRESS: case RECEIVE_ADDRESS_TYPE: case RECEIVE_RESPONSE_CODE: session.setEventMask(SelectionKey.OP_READ); break; case COMPLETE: break; } } private byte[] cred(final String cred) throws IOException { if (cred == null) { return new byte[] {}; } // These will remain with ISO-8859-1 since the RFC does not mention any string // to octet encoding. So neither one is wrong or right. final byte[] bytes = cred.getBytes(StandardCharsets.ISO_8859_1); if (bytes.length >= 255) { throw new IOException("SOCKS username / password are too long"); } return bytes; } @Override public void inputReady(final IOSession session, final ByteBuffer src) throws IOException { if (src != null) { try { this.buffer.put(src); } catch (final BufferOverflowException ex) { throw new IOException("Unexpected input data"); } } switch (this.state) { case RECEIVE_AUTH_METHOD: if (fillBuffer(session)) { this.buffer.flip(); final byte serverVersion = this.buffer.get(); final byte serverMethod = this.buffer.get(); if (serverVersion != CLIENT_VERSION) { throw new IOException("SOCKS server returned unsupported version: " + serverVersion); } if (serverMethod == USERNAME_PASSWORD) { this.buffer.clear(); final byte[] username = cred(reactorConfig.getSocksProxyUsername()); final byte[] password = cred(reactorConfig.getSocksProxyPassword()); setBufferLimit(username.length + password.length + 3); this.buffer.put(USERNAME_PASSWORD_VERSION); this.buffer.put((byte) username.length); this.buffer.put(username); this.buffer.put((byte) password.length); this.buffer.put(password); this.buffer.flip(); session.setEventMask(SelectionKey.OP_WRITE); this.state = State.SEND_USERNAME_PASSWORD; } else if (serverMethod == NO_AUTHENTICATION_REQUIRED) { prepareConnectCommand(); session.setEventMask(SelectionKey.OP_WRITE); this.state = State.SEND_CONNECT; } else { throw new IOException("SOCKS server return unsupported authentication method: " + serverMethod); } } break; case RECEIVE_AUTH: if (fillBuffer(session)) { this.buffer.flip(); this.buffer.get(); // skip server auth version final byte status = this.buffer.get(); if (status != SUCCESS) { throw new IOException("Authentication failed for external SOCKS proxy"); } prepareConnectCommand(); session.setEventMask(SelectionKey.OP_WRITE); this.state = State.SEND_CONNECT; } break; case RECEIVE_RESPONSE_CODE: if (fillBuffer(session)) { this.buffer.flip(); final byte serverVersion = this.buffer.get(); final byte responseCode = this.buffer.get(); if (serverVersion != CLIENT_VERSION) { throw new IOException("SOCKS server returned unsupported version: " + serverVersion); } switch (responseCode) { case SUCCESS: break; case 1: throw new IOException("SOCKS: General SOCKS server failure"); case 2: throw new IOException("SOCKS5: Connection not allowed by ruleset"); case 3: throw new IOException("SOCKS5: Network unreachable"); case 4: throw new IOException("SOCKS5: Host unreachable"); case 5: throw new IOException("SOCKS5: Connection refused"); case 6: throw new IOException("SOCKS5: TTL expired"); case 7: throw new IOException("SOCKS5: Command not supported"); case 8: throw new IOException("SOCKS5: Address type not supported"); default: throw new IOException("SOCKS5: Unexpected SOCKS response code " + responseCode); } this.buffer.compact(); this.buffer.limit(3); this.state = State.RECEIVE_ADDRESS_TYPE; // deliberate fall-through } else { break; } case RECEIVE_ADDRESS_TYPE: if (fillBuffer(session)) { this.buffer.flip(); this.buffer.get(); // reserved byte that has no purpose final byte aType = this.buffer.get(); final int addressSize; if (aType == InetAddressUtils.IPV4) { addressSize = 4; } else if (aType == InetAddressUtils.IPV6) { addressSize = 16; } else if (aType == ATYP_DOMAINNAME) { // mask with 0xFF to convert to unsigned byte value addressSize = this.buffer.get() & 0xFF; } else { throw new IOException("SOCKS server returned unsupported address type: " + aType); } final int remainingResponseSize = addressSize + 2; this.buffer.compact(); // make sure we only read what we need to, don't read too much this.buffer.limit(remainingResponseSize); this.state = State.RECEIVE_ADDRESS; // deliberate fall-through } else { break; } case RECEIVE_ADDRESS: if (fillBuffer(session)) { this.buffer.clear(); this.state = State.COMPLETE; final IOEventHandler newHandler = this.eventHandlerFactory.createHandler(dataChannel, sessionRequest.attachment); dataChannel.upgrade(newHandler); sessionRequest.completed(dataChannel); dataChannel.handleIOEvent(SelectionKey.OP_CONNECT); } break; case SEND_AUTH: case SEND_USERNAME_PASSWORD: case SEND_CONNECT: session.setEventMask(SelectionKey.OP_WRITE); break; case COMPLETE: break; } } private void prepareConnectCommand() throws IOException { this.buffer.clear(); setBufferLimit(MAX_COMMAND_CONNECT_LENGTH); this.buffer.put(CLIENT_VERSION); this.buffer.put(COMMAND_CONNECT); this.buffer.put((byte) 0); // reserved if (!(sessionRequest.remoteAddress instanceof InetSocketAddress)) { throw new IOException("Unsupported address class: " + sessionRequest.remoteAddress.getClass()); } final InetSocketAddress targetAddress = (InetSocketAddress) sessionRequest.remoteAddress; if (targetAddress.isUnresolved()) { this.buffer.put(ATYP_DOMAINNAME); final String hostName = targetAddress.getHostName(); final byte[] hostnameBytes = hostName.getBytes(StandardCharsets.US_ASCII); if (hostnameBytes.length > MAX_DNS_NAME_LENGTH) { throw new IOException("Host name exceeds " + MAX_DNS_NAME_LENGTH + " bytes"); } this.buffer.put((byte) hostnameBytes.length); this.buffer.put(hostnameBytes); } else { final InetAddress address = targetAddress.getAddress(); if (address instanceof Inet4Address) { this.buffer.put(InetAddressUtils.IPV4); } else if (address instanceof Inet6Address) { this.buffer.put(InetAddressUtils.IPV6); } else { throw new IOException("Unsupported remote address class: " + address.getClass().getName()); } this.buffer.put(address.getAddress()); } final int port = targetAddress.getPort(); this.buffer.putShort((short) port); this.buffer.flip(); } private void setBufferLimit(final int newLimit) { if (this.buffer.capacity() < newLimit) { final ByteBuffer newBuffer = ByteBuffer.allocate(newLimit); this.buffer.flip(); newBuffer.put(this.buffer); this.buffer = newBuffer; } else { this.buffer.limit(newLimit); } } private boolean writeAndPrepareRead(final ByteChannel channel, final int readSize) throws IOException { if (writeBuffer(channel)) { this.buffer.clear(); setBufferLimit(readSize); return true; } return false; } private boolean writeBuffer(final ByteChannel channel) throws IOException { if (this.buffer.hasRemaining()) { channel.write(this.buffer); } return !this.buffer.hasRemaining(); } private boolean fillBuffer(final ByteChannel channel) throws IOException { if (this.buffer.hasRemaining()) { final int bytesRead = channel.read(this.buffer); if (bytesRead < 0) { throw new ConnectionClosedException("SOCKS proxy closed the connection"); } } return !this.buffer.hasRemaining(); } @Override public void timeout(final IOSession session, final Timeout timeout) throws IOException { exception(session, SocketTimeoutExceptionFactory.create(timeout)); } @Override public void exception(final IOSession session, final Exception cause) { try { sessionRequest.failed(cause); } finally { session.close(CloseMode.IMMEDIATE); CommandSupport.failCommands(session, cause); } } @Override public void disconnected(final IOSession session) { sessionRequest.cancel(); CommandSupport.cancelCommands(session); } } httpcore5/src/main/java/org/apache/hc/core5/reactor/SocksProxyProtocolHandlerFactory.java0100644 0000000 0000000 00000004766 00000250600 030566 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; /** * @deprecated Use {@link IOReactorConfig}. * * As of version 5.0.1 {@link #createHandler(ProtocolIOSession, Object)} throws {@link UnsupportedOperationException}. */ @Deprecated public class SocksProxyProtocolHandlerFactory implements IOEventHandlerFactory { private final InetSocketAddress targetAddress; private final String username; private final String password; private final IOEventHandlerFactory eventHandlerFactory; public SocksProxyProtocolHandlerFactory(final SocketAddress targetAddress, final String username, final String password, final IOEventHandlerFactory eventHandlerFactory) throws IOException { this.eventHandlerFactory = eventHandlerFactory; this.username = username; this.password = password; if (targetAddress instanceof InetSocketAddress) { this.targetAddress = (InetSocketAddress) targetAddress; } else { throw new IOException("Unsupported target address type for SOCKS proxy connection: " + targetAddress.getClass()); } } @Override public IOEventHandler createHandler(final ProtocolIOSession ioSession, final Object attachment) { throw new UnsupportedOperationException(); } } httpcore5/src/main/java/org/apache/hc/core5/reactor/package-info.java0100644 0000000 0000000 00000002636 00000250600 024410 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Event driven network communication APIs and components loosely based on Doug Lea's reactor pattern. */ package org.apache.hc.core5.reactor; httpcore5/src/main/java/org/apache/hc/core5/reactor/ssl/0040755 0000000 0000000 00000000000 00000250600 022016 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/reactor/ssl/SSLBufferMode.java0100644 0000000 0000000 00000002431 00000250600 025256 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor.ssl; /** * @since 5.0 */ public enum SSLBufferMode { STATIC, DYNAMIC } httpcore5/src/main/java/org/apache/hc/core5/reactor/ssl/SSLIOSession.java0100644 0000000 0000000 00000104265 00000250600 025123 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor.ssl; import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.nio.channels.CancelledKeyException; import java.nio.channels.ClosedChannelException; import java.nio.channels.SelectionKey; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.SocketTimeoutExceptionFactory; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.reactor.Command; import org.apache.hc.core5.reactor.EventMask; import org.apache.hc.core5.reactor.IOEventHandler; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; import org.apache.hc.core5.util.Timeout; /** * {@code SSLIOSession} is a decorator class intended to transparently extend * an {@link IOSession} with transport layer security capabilities based on * the SSL/TLS protocol. * * @since 4.2 */ @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL) @Internal public class SSLIOSession implements IOSession { enum TLSHandShakeState { READY, INITIALIZED, HANDSHAKING, COMPLETE } private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0); private final NamedEndpoint targetEndpoint; private final IOSession session; private final SSLEngine sslEngine; private final SSLManagedBuffer inEncrypted; private final SSLManagedBuffer outEncrypted; private final SSLManagedBuffer inPlain; private final SSLSessionInitializer initializer; private final SSLSessionVerifier verifier; private final Callback sessionStartCallback; private final Callback sessionEndCallback; private final AtomicReference> handshakeCallbackRef; private final Timeout handshakeTimeout; private final SSLMode sslMode; private final AtomicInteger outboundClosedCount; private final AtomicReference handshakeStateRef; private final IOEventHandler internalEventHandler; private int appEventMask; private volatile boolean endOfStream; private volatile Status status = Status.ACTIVE; private volatile Timeout socketTimeout; private volatile TlsDetails tlsDetails; private volatile boolean appClosed; /** * Creates new instance of {@code SSLIOSession} class. * * @param session I/O session to be decorated with the TLS/SSL capabilities. * @param sslMode SSL mode (client or server) * @param targetEndpoint target endpoint (applicable in client mode only). May be {@code null}. * @param sslContext SSL context to use for this I/O session. * @param sslBufferMode buffer management mode * @param initializer optional SSL session initializer. May be {@code null}. * @param verifier optional SSL session verifier. May be {@code null}. * @param connectTimeout timeout to apply for the TLS/SSL handshake. May be {@code null}. * * @since 5.0 */ public SSLIOSession( final NamedEndpoint targetEndpoint, final IOSession session, final SSLMode sslMode, final SSLContext sslContext, final SSLBufferMode sslBufferMode, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier, final Callback sessionStartCallback, final Callback sessionEndCallback, final Timeout connectTimeout) { this(targetEndpoint, session, sslMode, sslContext, sslBufferMode, initializer, verifier, connectTimeout, sessionStartCallback, sessionEndCallback, null); } /** * Creates new instance of {@code SSLIOSession} class. * * @param session I/O session to be decorated with the TLS/SSL capabilities. * @param sslMode SSL mode (client or server) * @param targetEndpoint target endpoint (applicable in client mode only). May be {@code null}. * @param sslContext SSL context to use for this I/O session. * @param sslBufferMode buffer management mode * @param initializer optional SSL session initializer. May be {@code null}. * @param verifier optional SSL session verifier. May be {@code null}. * @param handshakeTimeout timeout to apply for the TLS/SSL handshake. May be {@code null}. * @param resultCallback result callback. May be {@code null}. * * @since 5.2 */ public SSLIOSession( final NamedEndpoint targetEndpoint, final IOSession session, final SSLMode sslMode, final SSLContext sslContext, final SSLBufferMode sslBufferMode, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier, final Timeout handshakeTimeout, final Callback sessionStartCallback, final Callback sessionEndCallback, final FutureCallback resultCallback) { super(); Args.notNull(session, "IO session"); Args.notNull(sslContext, "SSL context"); this.targetEndpoint = targetEndpoint; this.session = session; this.sslMode = sslMode; this.initializer = initializer; this.verifier = verifier; this.sessionStartCallback = sessionStartCallback; this.sessionEndCallback = sessionEndCallback; this.handshakeCallbackRef = new AtomicReference<>(resultCallback); this.appEventMask = session.getEventMask(); if (this.sslMode == SSLMode.CLIENT && targetEndpoint != null) { this.sslEngine = sslContext.createSSLEngine(targetEndpoint.getHostName(), targetEndpoint.getPort()); } else { this.sslEngine = sslContext.createSSLEngine(); } final SSLSession sslSession = this.sslEngine.getSession(); // Allocate buffers for network (encrypted) data final int netBufferSize = sslSession.getPacketBufferSize(); this.inEncrypted = SSLManagedBuffer.create(sslBufferMode, netBufferSize); this.outEncrypted = SSLManagedBuffer.create(sslBufferMode, netBufferSize); // Allocate buffers for application (unencrypted) data final int appBufferSize = sslSession.getApplicationBufferSize(); this.inPlain = SSLManagedBuffer.create(sslBufferMode, appBufferSize); this.outboundClosedCount = new AtomicInteger(0); this.handshakeStateRef = new AtomicReference<>(TLSHandShakeState.READY); this.handshakeTimeout = handshakeTimeout; this.internalEventHandler = new IOEventHandler() { @Override public void connected(final IOSession protocolSession) throws IOException { beginHandshake(protocolSession); } @Override public void inputReady(final IOSession protocolSession, final ByteBuffer src) throws IOException { receiveEncryptedData(); doHandshake(protocolSession); decryptData(protocolSession); updateEventMask(); } @Override public void outputReady(final IOSession protocolSession) throws IOException { encryptData(protocolSession); sendEncryptedData(); doHandshake(protocolSession); updateEventMask(); } @Override public void timeout(final IOSession protocolSession, final Timeout timeout) throws IOException { if (sslEngine.isInboundDone() && !sslEngine.isInboundDone()) { // The session failed to terminate cleanly close(CloseMode.IMMEDIATE); } if (handshakeStateRef.get() != TLSHandShakeState.COMPLETE) { exception(protocolSession, SocketTimeoutExceptionFactory.create(handshakeTimeout)); } else { ensureHandler().timeout(protocolSession, timeout); } } @Override public void exception(final IOSession protocolSession, final Exception cause) { final FutureCallback resultCallback = handshakeCallbackRef.getAndSet(null); if (resultCallback != null) { resultCallback.failed(cause); } final IOEventHandler handler = session.getHandler(); if (handshakeStateRef.get() != TLSHandShakeState.COMPLETE) { if (cause instanceof SSLHandshakeException) { close(CloseMode.GRACEFUL); } else { session.close(CloseMode.GRACEFUL); close(CloseMode.IMMEDIATE); } } if (handler != null) { handler.exception(protocolSession, cause); } } @Override public void disconnected(final IOSession protocolSession) { final IOEventHandler handler = session.getHandler(); if (handler != null) { handler.disconnected(protocolSession); } } }; } private IOEventHandler ensureHandler() { final IOEventHandler handler = session.getHandler(); Asserts.notNull(handler, "IO event handler"); return handler; } @Override public IOEventHandler getHandler() { return internalEventHandler; } public void beginHandshake(final IOSession protocolSession) throws IOException { if (handshakeStateRef.compareAndSet(TLSHandShakeState.READY, TLSHandShakeState.INITIALIZED)) { initialize(protocolSession); } } private void initialize(final IOSession protocolSession) throws IOException { // Save the initial socketTimeout of the underlying IOSession, to be restored after the handshake is finished this.socketTimeout = this.session.getSocketTimeout(); if (handshakeTimeout != null) { this.session.setSocketTimeout(handshakeTimeout); } this.session.getLock().lock(); try { if (this.status.compareTo(Status.CLOSING) >= 0) { return; } switch (this.sslMode) { case CLIENT: this.sslEngine.setUseClientMode(true); break; case SERVER: this.sslEngine.setUseClientMode(false); break; } if (this.initializer != null) { this.initializer.initialize(this.targetEndpoint, this.sslEngine); } this.handshakeStateRef.set(TLSHandShakeState.HANDSHAKING); this.sslEngine.beginHandshake(); this.inEncrypted.release(); this.outEncrypted.release(); doHandshake(protocolSession); updateEventMask(); } finally { this.session.getLock().unlock(); } } // A works-around for exception handling craziness in Sun/Oracle's SSLEngine // implementation. // // sun.security.pkcs11.wrapper.PKCS11Exception is re-thrown as // plain RuntimeException in sun.security.ssl.Handshaker#checkThrown private SSLException convert(final RuntimeException ex) { Throwable cause = ex.getCause(); if (cause == null) { cause = ex; } return new SSLException(cause); } private SSLEngineResult doWrap(final ByteBuffer src, final ByteBuffer dst) throws SSLException { try { return this.sslEngine.wrap(src, dst); } catch (final RuntimeException ex) { throw convert(ex); } } private SSLEngineResult doUnwrap(final ByteBuffer src, final ByteBuffer dst) throws SSLException { try { return this.sslEngine.unwrap(src, dst); } catch (final RuntimeException ex) { throw convert(ex); } } private void doRunTask() { final Runnable r = this.sslEngine.getDelegatedTask(); if (r != null) { r.run(); } } private void doHandshake(final IOSession protocolSession) throws IOException { boolean handshaking = true; SSLEngineResult result = null; while (handshaking) { HandshakeStatus handshakeStatus = this.sslEngine.getHandshakeStatus(); // Work-around for what appears to be a bug in Conscrypt SSLEngine that does not // transition into the handshaking state upon #closeOutbound() call but still // has some handshake data stuck in its internal buffer. if (handshakeStatus == HandshakeStatus.NOT_HANDSHAKING && outboundClosedCount.get() > 0) { handshakeStatus = HandshakeStatus.NEED_WRAP; } switch (handshakeStatus) { case NEED_WRAP: // Generate outgoing handshake data this.session.getLock().lock(); try { // Acquire buffers final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire(); // Just wrap an empty buffer because there is no data to write. result = doWrap(EMPTY_BUFFER, outEncryptedBuf); if (result.getStatus() != SSLEngineResult.Status.OK || result.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) { handshaking = false; } break; } finally { this.session.getLock().unlock(); } case NEED_UNWRAP: // Process incoming handshake data // Acquire buffers final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire(); final ByteBuffer inPlainBuf = this.inPlain.acquire(); // Perform operations inEncryptedBuf.flip(); try { result = doUnwrap(inEncryptedBuf, inPlainBuf); } finally { inEncryptedBuf.compact(); } try { if (!inEncryptedBuf.hasRemaining() && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) { throw new SSLException("Input buffer is full"); } } finally { // Release inEncrypted if empty if (inEncryptedBuf.position() == 0) { this.inEncrypted.release(); } } if (this.status.compareTo(Status.CLOSING) >= 0) { this.inPlain.release(); } if (result.getStatus() != SSLEngineResult.Status.OK) { handshaking = false; } break; case NEED_TASK: doRunTask(); break; case NOT_HANDSHAKING: handshaking = false; break; } } // The SSLEngine has just finished handshaking. This value is only generated by a call // to SSLEngine.wrap()/unwrap() when that call finishes a handshake. // It is never generated by SSLEngine.getHandshakeStatus(). if (result != null && result.getHandshakeStatus() == HandshakeStatus.FINISHED) { this.handshakeStateRef.set(TLSHandShakeState.COMPLETE); this.session.setSocketTimeout(this.socketTimeout); if (this.verifier != null) { this.tlsDetails = this.verifier.verify(this.targetEndpoint, this.sslEngine); } String applicationProtocol; if (this.tlsDetails == null) { final SSLSession sslSession = this.sslEngine.getSession(); try { applicationProtocol = this.sslEngine.getApplicationProtocol(); } catch (final UnsupportedOperationException e) { // If the underlying provider does not support the operation, the getApplicationProtocol() method throws an UnsupportedOperationException. // In this case, we fall back to "http/1.1" as the application protocol. // This is a workaround to allow older applications that do not support the getApplicationProtocol() method to continue working. // This workaround is temporary and is meant to maintain compatibility with older systems. applicationProtocol = "http/1.1"; } this.tlsDetails = new TlsDetails(sslSession, applicationProtocol); } ensureHandler().connected(protocolSession); if (this.sessionStartCallback != null) { this.sessionStartCallback.execute(this); } final FutureCallback resultCallback = handshakeCallbackRef.getAndSet(null); if (resultCallback != null) { resultCallback.completed(sslEngine.getSession()); } } } private void updateEventMask() { this.session.getLock().lock(); try { // Graceful session termination if (this.status == Status.ACTIVE && (this.endOfStream || this.sslEngine.isInboundDone())) { this.status = Status.CLOSING; final FutureCallback resultCallback = handshakeCallbackRef.getAndSet(null); if (resultCallback != null) { resultCallback.failed(new SSLHandshakeException("TLS handshake failed")); } } if (this.status == Status.CLOSING && !this.outEncrypted.hasData()) { this.sslEngine.closeOutbound(); this.outboundClosedCount.incrementAndGet(); } final HandshakeStatus handshakeStatus = this.sslEngine.getHandshakeStatus(); if (this.status == Status.CLOSING && (handshakeStatus == HandshakeStatus.NOT_HANDSHAKING || handshakeStatus == HandshakeStatus.FINISHED) && !this.outEncrypted.hasData() && this.sslEngine.isOutboundDone() && (this.endOfStream || this.sslEngine.isInboundDone()) && appClosed) { this.status = Status.CLOSED; } // Abnormal session termination if (this.status.compareTo(Status.CLOSING) <= 0 && this.endOfStream && handshakeStatus == HandshakeStatus.NEED_UNWRAP) { this.status = Status.CLOSED; } if (this.status == Status.CLOSED) { this.session.close(); if (sessionEndCallback != null) { sessionEndCallback.execute(this); } return; } // Is there a task pending? if (handshakeStatus == HandshakeStatus.NEED_TASK) { doRunTask(); } // Need to toggle the event mask for this channel? final int oldMask = this.session.getEventMask(); int newMask = oldMask; switch (this.sslEngine.getHandshakeStatus()) { case NEED_WRAP: newMask = EventMask.READ_WRITE; break; case NEED_UNWRAP: newMask = EventMask.READ; break; case NOT_HANDSHAKING: newMask = this.appEventMask; break; } if (this.endOfStream && !this.inPlain.hasData()) { newMask = newMask & ~EventMask.READ; } else if (this.status == Status.CLOSING) { newMask = newMask | EventMask.READ; } // Do we have encrypted data ready to be sent? if (this.outEncrypted.hasData()) { newMask = newMask | EventMask.WRITE; } // Update the mask if necessary if (oldMask != newMask) { this.session.setEventMask(newMask); } } finally { this.session.getLock().unlock(); } } private int sendEncryptedData() throws IOException { this.session.getLock().lock(); try { if (this.status == Status.ACTIVE && !this.outEncrypted.hasData()) { // If the buffer isn't acquired or is empty, call write() with an empty buffer. // This will ensure that tests performed by write() still take place without // having to acquire and release an empty buffer (e.g. connection closed, // interrupted thread, etc..) return this.session.write(EMPTY_BUFFER); } // Acquire buffer final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire(); // Clear output buffer if the session has been closed // in case there is still `close_notify` data stuck in it if (this.status == Status.CLOSED) { outEncryptedBuf.clear(); } // Perform operation int bytesWritten = 0; if (outEncryptedBuf.position() > 0) { outEncryptedBuf.flip(); try { bytesWritten = this.session.write(outEncryptedBuf); } finally { outEncryptedBuf.compact(); } } // Release if empty if (outEncryptedBuf.position() == 0) { this.outEncrypted.release(); } return bytesWritten; } finally { this.session.getLock().unlock(); } } private int receiveEncryptedData() throws IOException { if (this.endOfStream) { return -1; } // Acquire buffer final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire(); // Perform operation final int bytesRead = this.session.read(inEncryptedBuf); // Release if empty if (inEncryptedBuf.position() == 0) { this.inEncrypted.release(); } if (bytesRead == -1) { this.endOfStream = true; } return bytesRead; } private void decryptData(final IOSession protocolSession) throws IOException { final HandshakeStatus handshakeStatus = sslEngine.getHandshakeStatus(); if ((handshakeStatus == HandshakeStatus.NOT_HANDSHAKING || handshakeStatus == HandshakeStatus.FINISHED) && inEncrypted.hasData()) { final ByteBuffer inEncryptedBuf = inEncrypted.acquire(); inEncryptedBuf.flip(); try { while (inEncryptedBuf.hasRemaining()) { final ByteBuffer inPlainBuf = inPlain.acquire(); try { final SSLEngineResult result = doUnwrap(inEncryptedBuf, inPlainBuf); if (!inEncryptedBuf.hasRemaining() && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) { throw new SSLException("Unable to complete SSL handshake"); } if (sslEngine.isInboundDone()) { endOfStream = true; } if (inPlainBuf.position() > 0) { inPlainBuf.flip(); try { ensureHandler().inputReady(protocolSession, inPlainBuf.hasRemaining() ? inPlainBuf : null); } finally { inPlainBuf.clear(); } } if (result.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW && endOfStream) { throw new SSLException("Unable to decrypt incoming data due to unexpected end of stream"); } if (result.getStatus() != SSLEngineResult.Status.OK || result.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING && result.getHandshakeStatus() != HandshakeStatus.FINISHED) { break; } } finally { inPlain.release(); } } } finally { inEncryptedBuf.compact(); // Release inEncrypted if empty if (inEncryptedBuf.position() == 0) { inEncrypted.release(); } } } if (endOfStream && !inEncrypted.hasData()) { ensureHandler().inputReady(protocolSession, null); } } private void encryptData(final IOSession protocolSession) throws IOException { final boolean appReady; this.session.getLock().lock(); try { appReady = (this.appEventMask & SelectionKey.OP_WRITE) > 0 && this.status.compareTo(Status.CLOSED) < 0 && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING; } finally { this.session.getLock().unlock(); } if (appReady) { ensureHandler().outputReady(protocolSession); } } @Override public int write(final ByteBuffer src) throws IOException { Args.notNull(src, "Byte buffer"); this.session.getLock().lock(); try { if (this.status != Status.ACTIVE) { throw new ClosedChannelException(); } if (this.handshakeStateRef.get() == TLSHandShakeState.READY) { return 0; } final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire(); final SSLEngineResult result = doWrap(src, outEncryptedBuf); return result.bytesConsumed(); } finally { this.session.getLock().unlock(); } } @Override public int read(final ByteBuffer dst) { return endOfStream ? -1 : 0; } @Override public String getId() { return session.getId(); } @Override public Lock getLock() { return this.session.getLock(); } @Override public void upgrade(final IOEventHandler handler) { this.session.upgrade(handler); } public TlsDetails getTlsDetails() { return tlsDetails; } @Override public boolean isOpen() { return this.status == Status.ACTIVE && this.session.isOpen(); } @Override public void close() { close(CloseMode.GRACEFUL); } @Override public void close(final CloseMode closeMode) { this.session.getLock().lock(); try { appClosed = true; if (closeMode == CloseMode.GRACEFUL) { if (this.status.compareTo(Status.CLOSING) >= 0) { return; } this.status = Status.CLOSING; if (this.session.getSocketTimeout().isDisabled()) { this.session.setSocketTimeout(Timeout.ofMilliseconds(1000)); } try { // Catch all unchecked exceptions in case something goes wrong // in the JSSE provider. For instance // com.android.org.conscrypt.NativeCrypto#SSL_get_shutdown can // throw NPE at this point doHandshake(this); sendEncryptedData(); updateEventMask(); } catch (final CancelledKeyException ex) { this.session.close(CloseMode.GRACEFUL); } catch (final Exception ex) { this.session.close(CloseMode.IMMEDIATE); } } else { if (this.status == Status.CLOSED) { return; } this.inEncrypted.release(); this.outEncrypted.release(); this.inPlain.release(); this.status = Status.CLOSED; this.session.close(closeMode); } } finally { this.session.getLock().unlock(); } } @Override public Status getStatus() { return this.status; } @Override public void enqueue(final Command command, final Command.Priority priority) { this.session.getLock().lock(); try { this.session.enqueue(command, priority); if (isOpen()) { setEvent(SelectionKey.OP_WRITE); } else { command.cancel(); } } finally { this.session.getLock().unlock(); } } @Override public boolean hasCommands() { return this.session.hasCommands(); } @Override public Command poll() { return this.session.poll(); } @Override public ByteChannel channel() { return this.session.channel(); } @Override public SocketAddress getLocalAddress() { return this.session.getLocalAddress(); } @Override public SocketAddress getRemoteAddress() { return this.session.getRemoteAddress(); } @Override public int getEventMask() { this.session.getLock().lock(); try { return this.appEventMask; } finally { this.session.getLock().unlock(); } } @Override public void setEventMask(final int ops) { this.session.getLock().lock(); try { this.appEventMask = ops; updateEventMask(); } finally { this.session.getLock().unlock(); } } @Override public void setEvent(final int op) { this.session.getLock().lock(); try { this.appEventMask = this.appEventMask | op; updateEventMask(); } finally { this.session.getLock().unlock(); } } @Override public void clearEvent(final int op) { this.session.getLock().lock(); try { this.appEventMask = this.appEventMask & ~op; updateEventMask(); } finally { this.session.getLock().unlock(); } } @Override public Timeout getSocketTimeout() { return this.session.getSocketTimeout(); } @Override public void setSocketTimeout(final Timeout timeout) { this.socketTimeout = timeout; if (this.sslEngine.getHandshakeStatus() == HandshakeStatus.FINISHED) { this.session.setSocketTimeout(timeout); } } @Override public void updateReadTime() { this.session.updateReadTime(); } @Override public void updateWriteTime() { this.session.updateWriteTime(); } @Override public long getLastReadTime() { return this.session.getLastReadTime(); } @Override public long getLastWriteTime() { return this.session.getLastWriteTime(); } @Override public long getLastEventTime() { return this.session.getLastEventTime(); } private static void formatOps(final StringBuilder buffer, final int ops) { if ((ops & SelectionKey.OP_READ) > 0) { buffer.append('r'); } if ((ops & SelectionKey.OP_WRITE) > 0) { buffer.append('w'); } } @Override public String toString() { this.session.getLock().lock(); try { final StringBuilder buffer = new StringBuilder(); buffer.append(this.session); buffer.append("["); buffer.append(this.status); buffer.append("]["); formatOps(buffer, this.appEventMask); buffer.append("]["); buffer.append(this.sslEngine.getHandshakeStatus()); if (this.sslEngine.isInboundDone()) { buffer.append("][inbound done]["); } if (this.sslEngine.isOutboundDone()) { buffer.append("][outbound done]["); } if (this.endOfStream) { buffer.append("][EOF]["); } buffer.append("]["); buffer.append(!this.inEncrypted.hasData() ? 0 : inEncrypted.acquire().position()); buffer.append("]["); buffer.append(!this.inPlain.hasData() ? 0 : inPlain.acquire().position()); buffer.append("]["); buffer.append(!this.outEncrypted.hasData() ? 0 : outEncrypted.acquire().position()); buffer.append("]"); return buffer.toString(); } finally { this.session.getLock().unlock(); } } } httpcore5/src/main/java/org/apache/hc/core5/reactor/ssl/SSLManagedBuffer.java0100644 0000000 0000000 00000007711 00000250600 025734 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor.ssl; import java.nio.ByteBuffer; import org.apache.hc.core5.util.Args; abstract class SSLManagedBuffer { /** * Allocates the resources required for this buffer, or returns the resources already allocated for this buffer. * Unless {@link #release() } is called, multiple invocations to this method must return the same * {@link java.nio.ByteBuffer}. * @return buffer */ abstract ByteBuffer acquire(); /** * Releases the resources for this buffer. If the buffer has already been released, this method does nothing. */ abstract void release(); /** * Tests to see if this buffer has been acquired. * @return {@code true} if the buffer is acquired, otherwise {@code false} */ abstract boolean isAcquired(); /** * Tests to make sure that the buffer has been acquired and the underlying buffer has a position larger than * {@code 0}. Essentially the same as {@code isAquired() && acquire().position > 0}. * @return {@code true} if the buffer has been acquired and the underlying buffer's position is {@code > 0}, * otherwise {@code false} */ abstract boolean hasData(); static SSLManagedBuffer create(final SSLBufferMode mode, final int size) { return mode == SSLBufferMode.DYNAMIC ? new DynamicBuffer(size) : new StaticBuffer(size); } static final class StaticBuffer extends SSLManagedBuffer { private final ByteBuffer buffer; public StaticBuffer(final int size) { Args.positive(size, "size"); buffer = ByteBuffer.allocate(size); } @Override public ByteBuffer acquire() { return buffer; } @Override public void release() { // do nothing } @Override public boolean isAcquired() { return true; } @Override public boolean hasData() { return buffer.position() > 0; } } static final class DynamicBuffer extends SSLManagedBuffer { private ByteBuffer wrapped; private final int length; public DynamicBuffer(final int size) { Args.positive(size, "size"); this.length = size; } @Override public ByteBuffer acquire() { if (wrapped != null) { return wrapped; } wrapped = ByteBuffer.allocate(length); return wrapped; } @Override public void release() { wrapped = null; } @Override public boolean isAcquired() { return wrapped != null; } @Override public boolean hasData() { return wrapped != null && wrapped.position() > 0; } } } httpcore5/src/main/java/org/apache/hc/core5/reactor/ssl/SSLMode.java0100644 0000000 0000000 00000002422 00000250600 024124 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor.ssl; /** * @since 4.2 */ public enum SSLMode { CLIENT, SERVER } httpcore5/src/main/java/org/apache/hc/core5/reactor/ssl/SSLSessionInitializer.java0100644 0000000 0000000 00000003620 00000250600 027070 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor.ssl; import javax.net.ssl.SSLEngine; import org.apache.hc.core5.net.NamedEndpoint; /** * Callback interface that can be used to customize TLS/SSL session initialization. * * @since 4.2 */ public interface SSLSessionInitializer { /** * Triggered when the SSL connection is being initialized. Custom handlers * can use this callback to customize properties of the {@link javax.net.ssl.SSLEngine} * used to establish the SSL session. * * @param endpoint the endpoint name for a client side session or {@code null} * for a server side session. * @param sslEngine the SSL engine. */ void initialize(NamedEndpoint endpoint, SSLEngine sslEngine); } httpcore5/src/main/java/org/apache/hc/core5/reactor/ssl/SSLSessionVerifier.java0100644 0000000 0000000 00000004216 00000250600 026362 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor.ssl; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import org.apache.hc.core5.net.NamedEndpoint; /** * Callback interface that can be used to customize TLS/SSL session verification. * * @since 4.2 */ public interface SSLSessionVerifier { /** * Triggered when the SSL connection has been established and initial SSL * handshake has been successfully completed. Custom handlers can use * this callback to verify properties of the {@link SSLEngine}. * For instance this would be the right place to enforce SSL cipher * strength, validate certificate chain and do hostname checks. * * @param endpoint the endpoint name for a client side session or {@code null} * for a server side session. * @param sslEngine SSL engine. * @throws SSLException if case of SSL protocol error. */ TlsDetails verify(NamedEndpoint endpoint, SSLEngine sslEngine) throws SSLException; } httpcore5/src/main/java/org/apache/hc/core5/reactor/ssl/TlsDetails.java0100644 0000000 0000000 00000003767 00000250600 024743 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor.ssl; import javax.net.ssl.SSLSession; /** * Represents details of a fully established TLS session. * * @since 5.0 */ public final class TlsDetails { private final SSLSession sslSession; private final String applicationProtocol; public TlsDetails(final SSLSession sslSession, final String applicationProtocol) { this.sslSession = sslSession; this.applicationProtocol = applicationProtocol; } public SSLSession getSSLSession() { return sslSession; } public String getApplicationProtocol() { return applicationProtocol; } @Override public String toString() { return "TlsDetails{" + "sslSession=" + sslSession + ", applicationProtocol='" + applicationProtocol + '\'' + '}'; } } httpcore5/src/main/java/org/apache/hc/core5/reactor/ssl/TransportSecurityLayer.java0100644 0000000 0000000 00000010053 00000250600 027376 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor.ssl; import javax.net.ssl.SSLContext; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.util.Timeout; /** * TLS capable session layer interface. * * @since 5.0 */ public interface TransportSecurityLayer { /** * Starts TLS session over an existing network connection with the given SSL context. * {@link NamedEndpoint} details are applicable for client side connections and * are used for host name verification, when supported by the SSL engine. * * @param sslContext SSL context to be used for this session. * @param endpoint optional endpoint details for outgoing client side connections. * @param sslBufferMode SSL buffer management mode. * @param initializer SSL session initialization callback. * @param verifier SSL session verification callback. * @param handshakeTimeout the timeout to use while performing the TLS handshake; may be {@code null}. * @throws UnsupportedOperationException Thrown the requested operation is not supported. */ void startTls( SSLContext sslContext, NamedEndpoint endpoint, SSLBufferMode sslBufferMode, SSLSessionInitializer initializer, SSLSessionVerifier verifier, Timeout handshakeTimeout) throws UnsupportedOperationException; /** * Starts TLS session over an existing network connection with the given SSL context. * {@link NamedEndpoint} details are applicable for client side connections and * are used for host name verification, when supported by the SSL engine. * * @param sslContext SSL context to be used for this session. * @param endpoint optional endpoint details for outgoing client side connections. * @param sslBufferMode SSL buffer management mode. * @param initializer SSL session initialization callback. * @param verifier SSL session verification callback. * @param handshakeTimeout the timeout to use while performing the TLS handshake; may be {@code null}. * @throws UnsupportedOperationException Thrown the requested operation is not supported. * @since 5.2 */ default void startTls( SSLContext sslContext, NamedEndpoint endpoint, SSLBufferMode sslBufferMode, SSLSessionInitializer initializer, SSLSessionVerifier verifier, Timeout handshakeTimeout, FutureCallback callback) throws UnsupportedOperationException { startTls(sslContext, endpoint, sslBufferMode, initializer, verifier, handshakeTimeout); if (callback != null) { callback.completed(null); } } /** * Gets the details of a fully established TLS session. * * @return TLS session details. */ TlsDetails getTlsDetails(); } httpcore5/src/main/java/org/apache/hc/core5/reactor/ssl/package-info.java0100644 0000000 0000000 00000002367 00000250600 025212 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * TLS/SSL support for I/O reactors. */ package org.apache.hc.core5.reactor.ssl; httpcore5/src/main/java/org/apache/hc/core5/ssl/0040755 0000000 0000000 00000000000 00000250600 020357 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/ssl/PrivateKeyDetails.java0100644 0000000 0000000 00000003632 00000250600 024614 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.ssl; import java.security.cert.X509Certificate; import java.util.Arrays; import org.apache.hc.core5.util.Args; /** * Private key details. * * @since 4.4 */ public final class PrivateKeyDetails { private final String type; private final X509Certificate[] certChain; public PrivateKeyDetails(final String type, final X509Certificate[] certChain) { super(); this.type = Args.notNull(type, "Private key type"); this.certChain = certChain; } public String getType() { return type; } public X509Certificate[] getCertChain() { return certChain; } @Override public String toString() { return type + ':' + Arrays.toString(certChain); } } httpcore5/src/main/java/org/apache/hc/core5/ssl/PrivateKeyStrategy.java0100644 0000000 0000000 00000003074 00000250600 025031 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.ssl; import java.util.Map; import javax.net.ssl.SSLParameters; /** * A strategy allowing for a choice of an alias during SSL authentication. * * @since 4.4 */ public interface PrivateKeyStrategy { /** * Determines what key material to use for SSL authentication. */ String chooseAlias(Map aliases, SSLParameters sslParameters); } httpcore5/src/main/java/org/apache/hc/core5/ssl/SSLContextBuilder.java0100644 0000000 0000000 00000071152 00000250600 024542 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.ssl; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.Socket; import java.net.URL; import java.nio.file.Files; import java.nio.file.OpenOption; import java.nio.file.Path; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Principal; import java.security.PrivateKey; import java.security.Provider; import java.security.SecureRandom; import java.security.Security; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509TrustManager; import org.apache.hc.core5.util.Args; /** * Builder for {@link javax.net.ssl.SSLContext} instances. *

* Please note: the default Oracle JSSE implementation of {@link SSLContext#init(KeyManager[], TrustManager[], SecureRandom)} * accepts multiple key and trust managers, however only only first matching type is ever used. * See for example: * * SSLContext.html#init * * * @since 4.4 */ public class SSLContextBuilder { static final String TLS = "TLS"; private String protocol; private final Set keyManagers; private String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); private String keyStoreType = KeyStore.getDefaultType(); private final Set trustManagers; private String trustManagerFactoryAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); private SecureRandom secureRandom; private Provider provider; private Provider tsProvider; private Provider ksProvider; /** * An empty immutable {@code KeyManager} array. */ private static final KeyManager[] EMPTY_KEY_MANAGER_ARRAY = {}; /** * An empty immutable {@code TrustManager} array. */ private static final TrustManager[] EMPTY_TRUST_MANAGER_ARRAY = {}; public static SSLContextBuilder create() { return new SSLContextBuilder(); } public SSLContextBuilder() { this.keyManagers = new LinkedHashSet<>(); this.trustManagers = new LinkedHashSet<>(); } /** * Sets the SSLContext algorithm name. * * @param protocol * the SSLContext algorithm name of the requested protocol. See * the SSLContext section in the Java * Cryptography Architecture Standard Algorithm Name * Documentation for more information. * @return this instance. * @see Java * Cryptography Architecture Standard Algorithm Name Documentation */ public SSLContextBuilder setProtocol(final String protocol) { this.protocol = protocol; return this; } public SSLContextBuilder setProvider(final Provider provider) { this.provider = provider; return this; } public SSLContextBuilder setProvider(final String name) { this.provider = Security.getProvider(name); return this; } /** * Sets the JCA provider to use for creating trust stores. * * @param provider provider to use for creating trust stores. * @return this instance. * @since 5.2 */ public SSLContextBuilder setTrustStoreProvider(final Provider provider) { this.tsProvider = provider; return this; } /** * Sets the JCA provider name to use for creating trust stores. * * @param name Name of the provider to use for creating trust stores, the provider must be registered with the JCA. * @return this instance. * @throws NoSuchProviderException if no provider with the specified name is installed or if name is null. * @since 5.2 */ public SSLContextBuilder setTrustStoreProvider(final String name) throws NoSuchProviderException { this.tsProvider = requireNonNullProvider(name); return this; } /** * Sets the JCA provider to use for creating key stores. * * @param provider provider to use for creating key stores. * @return this instance. * @since 5.2 */ public SSLContextBuilder setKeyStoreProvider(final Provider provider) { this.ksProvider = provider; return this; } /** * Sets the JCA provider name to use for creating key stores. * * @param name Name of the provider to use for creating key stores, the provider must be registered with the JCA. * @return this instance. * @throws NoSuchProviderException if no provider with the specified name is installed or if name is null. * @since 5.2 */ public SSLContextBuilder setKeyStoreProvider(final String name) throws NoSuchProviderException { this.ksProvider = requireNonNullProvider(name); return this; } /** * Sets the key store type. * * @param keyStoreType * the SSLkey store type. See * the KeyStore section in the Java * Cryptography Architecture Standard Algorithm Name * Documentation for more information. * @return this instance. * @see Java * Cryptography Architecture Standard Algorithm Name Documentation * @since 4.4.7 */ public SSLContextBuilder setKeyStoreType(final String keyStoreType) { this.keyStoreType = keyStoreType; return this; } /** * Sets the key manager factory algorithm name. * * @param keyManagerFactoryAlgorithm * the key manager factory algorithm name of the requested protocol. See * the KeyManagerFactory section in the Java * Cryptography Architecture Standard Algorithm Name * Documentation for more information. * @return this instance. * @see Java * Cryptography Architecture Standard Algorithm Name Documentation * @since 4.4.7 */ public SSLContextBuilder setKeyManagerFactoryAlgorithm(final String keyManagerFactoryAlgorithm) { this.keyManagerFactoryAlgorithm = keyManagerFactoryAlgorithm; return this; } /** * Sets the trust manager factory algorithm name. * * @param trustManagerFactoryAlgorithm * the trust manager algorithm name of the requested protocol. See * the TrustManagerFactory section in the Java * Cryptography Architecture Standard Algorithm Name * Documentation for more information. * @return this instance. * @see Java * Cryptography Architecture Standard Algorithm Name Documentation * @since 4.4.7 */ public SSLContextBuilder setTrustManagerFactoryAlgorithm(final String trustManagerFactoryAlgorithm) { this.trustManagerFactoryAlgorithm = trustManagerFactoryAlgorithm; return this; } public SSLContextBuilder setSecureRandom(final SecureRandom secureRandom) { this.secureRandom = secureRandom; return this; } /** * @param trustStrategy * custom trust strategy to use; can be {@code null} in which case * only the default trust managers will be used * @return this instance. * @throws NoSuchAlgorithmException if no Provider supports a KeyManagerFactorySpi implementation for the specified algorithm. * @throws KeyStoreException if a TrustManagerFactory operation fails. */ public SSLContextBuilder loadTrustMaterial( final KeyStore trustStore, final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException { final String alg = trustManagerFactoryAlgorithm == null ? TrustManagerFactory.getDefaultAlgorithm() : trustManagerFactoryAlgorithm; final TrustManagerFactory tmFactory = tsProvider == null ? TrustManagerFactory.getInstance(alg) : TrustManagerFactory.getInstance(alg, tsProvider); tmFactory.init(trustStore); final TrustManager[] tms = tmFactory.getTrustManagers(); if (tms != null) { if (trustStrategy != null) { for (int i = 0; i < tms.length; i++) { final TrustManager tm = tms[i]; if (tm instanceof X509TrustManager) { tms[i] = new TrustManagerDelegate((X509TrustManager) tm, trustStrategy); } } } Collections.addAll(this.trustManagers, tms); } return this; } /** * @return this instance. * @throws NoSuchAlgorithmException if no Provider supports a KeyManagerFactorySpi implementation for the specified algorithm. * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type. * @throws CertificateException if any of the certificates in the keystore could not be loaded. * @throws IOException if an I/O exception occurs. * @since 5.2 */ public SSLContextBuilder loadTrustMaterial( final Path file) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException { return loadTrustMaterial(file, null); } /** * @return this instance. * @throws NoSuchAlgorithmException if no Provider supports a KeyManagerFactorySpi implementation for the specified algorithm. * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type. * @throws CertificateException if any of the certificates in the keystore could not be loaded. * @throws IOException if an I/O exception occurs. * @since 5.2 */ public SSLContextBuilder loadTrustMaterial( final Path file, final char[] storePassword) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException { return loadTrustMaterial(file, storePassword, null); } /** * @return this instance. * @throws NoSuchAlgorithmException if no Provider supports a KeyManagerFactorySpi implementation for the specified algorithm. * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type. * @throws CertificateException if any of the certificates in the keystore could not be loaded. * @throws IOException if an I/O exception occurs. * @since 5.2 */ public SSLContextBuilder loadTrustMaterial( final Path file, final char[] storePassword, final TrustStrategy trustStrategy, final OpenOption... openOptions) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException { Args.notNull(file, "Truststore file"); return loadTrustMaterial(loadKeyStore(file, storePassword, openOptions), trustStrategy); } public SSLContextBuilder loadTrustMaterial( final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException { return loadTrustMaterial(null, trustStrategy); } public SSLContextBuilder loadTrustMaterial( final File file, final char[] storePassword, final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException { Args.notNull(file, "Truststore file"); return loadTrustMaterial(file.toPath(), storePassword, trustStrategy); } public SSLContextBuilder loadTrustMaterial( final File file, final char[] storePassword) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException { return loadTrustMaterial(file, storePassword, null); } public SSLContextBuilder loadTrustMaterial( final File file) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException { return loadTrustMaterial(file, null); } public SSLContextBuilder loadTrustMaterial( final URL url, final char[] storePassword, final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException { Args.notNull(url, "Truststore URL"); return loadTrustMaterial(loadKeyStore(url, storePassword), trustStrategy); } public SSLContextBuilder loadTrustMaterial( final URL url, final char[] storePassword) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException { return loadTrustMaterial(url, storePassword, null); } public SSLContextBuilder loadKeyMaterial( final KeyStore keyStore, final char[] keyPassword, final PrivateKeyStrategy aliasStrategy) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException { final String alg = keyManagerFactoryAlgorithm == null ? KeyManagerFactory.getDefaultAlgorithm() : keyManagerFactoryAlgorithm; final KeyManagerFactory kmFactory = ksProvider == null ? KeyManagerFactory.getInstance(alg) : KeyManagerFactory.getInstance(alg, ksProvider); kmFactory.init(keyStore, keyPassword); final KeyManager[] kms = kmFactory.getKeyManagers(); if (kms != null) { if (aliasStrategy != null) { for (int i = 0; i < kms.length; i++) { final KeyManager km = kms[i]; if (km instanceof X509ExtendedKeyManager) { kms[i] = new KeyManagerDelegate((X509ExtendedKeyManager) km, aliasStrategy); } } } Collections.addAll(keyManagers, kms); } return this; } /** * @return this instance. * @throws NoSuchAlgorithmException if no Provider supports a KeyManagerFactorySpi implementation for the specified algorithm. * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type. * @throws CertificateException if any of the certificates in the keystore could not be loaded. * @throws IOException if an I/O exception occurs. * @throws UnrecoverableKeyException if the key cannot be recovered (for example, the given password is wrong). * @since 5.2 */ public SSLContextBuilder loadKeyMaterial( final Path file, final char[] storePassword, final char[] keyPassword, final OpenOption... openOptions) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException { return loadKeyMaterial(file, storePassword, keyPassword, null, openOptions); } /** * @return this instance. * @throws NoSuchAlgorithmException if no Provider supports a KeyManagerFactorySpi implementation for the specified algorithm. * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type. * @throws CertificateException if any of the certificates in the keystore could not be loaded. * @throws IOException if an I/O exception occurs. * @throws UnrecoverableKeyException if the key cannot be recovered (for example, the given password is wrong). * @since 5.2 */ public SSLContextBuilder loadKeyMaterial( final Path file, final char[] storePassword, final char[] keyPassword, final PrivateKeyStrategy aliasStrategy, final OpenOption... openOptions) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException { Args.notNull(file, "Keystore file"); return loadKeyMaterial(loadKeyStore(file, storePassword, openOptions), keyPassword, aliasStrategy); } public SSLContextBuilder loadKeyMaterial( final KeyStore keyStore, final char[] keyPassword) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException { return loadKeyMaterial(keyStore, keyPassword, null); } public SSLContextBuilder loadKeyMaterial( final File file, final char[] storePassword, final char[] keyPassword, final PrivateKeyStrategy aliasStrategy) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException { Args.notNull(file, "Keystore file"); return loadKeyMaterial(file.toPath(), storePassword, keyPassword, aliasStrategy); } public SSLContextBuilder loadKeyMaterial( final File file, final char[] storePassword, final char[] keyPassword) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException { return loadKeyMaterial(file, storePassword, keyPassword, null); } public SSLContextBuilder loadKeyMaterial( final URL url, final char[] storePassword, final char[] keyPassword, final PrivateKeyStrategy aliasStrategy) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException { Args.notNull(url, "Keystore URL"); return loadKeyMaterial(loadKeyStore(url, storePassword), keyPassword, aliasStrategy); } public SSLContextBuilder loadKeyMaterial( final URL url, final char[] storePassword, final char[] keyPassword) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, CertificateException, IOException { return loadKeyMaterial(url, storePassword, keyPassword, null); } /** * @throws KeyManagementException if this SSLContext operation fails. */ protected void initSSLContext( final SSLContext sslContext, final Collection keyManagers, final Collection trustManagers, final SecureRandom secureRandom) throws KeyManagementException { sslContext.init( !keyManagers.isEmpty() ? keyManagers.toArray(EMPTY_KEY_MANAGER_ARRAY) : null, !trustManagers.isEmpty() ? trustManagers.toArray(EMPTY_TRUST_MANAGER_ARRAY) : null, secureRandom); } /** * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type. * @throws IOException if an I/O error occurs. * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found. * @throws CertificateException if any of the certificates in the keystore could not be loaded */ private KeyStore loadKeyStore(final Path file, final char[] password, final OpenOption... openOptions) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { final KeyStore keyStore = KeyStore.getInstance(keyStoreType); try (final InputStream inputStream = Files.newInputStream(file, openOptions)) { keyStore.load(inputStream, password); } return keyStore; } private KeyStore loadKeyStore(final URL url, final char[] password) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { final KeyStore keyStore = KeyStore.getInstance(keyStoreType); try (final InputStream inputStream = url.openStream()) { keyStore.load(inputStream, password); } return keyStore; } /** * Builds a new SSLContext. * * @return a new SSLContext. * @throws NoSuchAlgorithmException if no Provider supports a KeyManagerFactorySpi implementation for the specified algorithm. * @throws KeyManagementException if this SSLContext operation fails. */ public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException { final SSLContext sslContext; final String protocolStr = this.protocol != null ? this.protocol : TLS; if (this.provider != null) { sslContext = SSLContext.getInstance(protocolStr, this.provider); } else { sslContext = SSLContext.getInstance(protocolStr); } initSSLContext(sslContext, keyManagers, trustManagers, secureRandom); return sslContext; } static class TrustManagerDelegate implements X509TrustManager { private final X509TrustManager trustManager; private final TrustStrategy trustStrategy; TrustManagerDelegate(final X509TrustManager trustManager, final TrustStrategy trustStrategy) { this.trustManager = trustManager; this.trustStrategy = trustStrategy; } @Override public void checkClientTrusted( final X509Certificate[] chain, final String authType) throws CertificateException { this.trustManager.checkClientTrusted(chain, authType); } @Override public void checkServerTrusted( final X509Certificate[] chain, final String authType) throws CertificateException { if (!this.trustStrategy.isTrusted(chain, authType)) { this.trustManager.checkServerTrusted(chain, authType); } } @Override public X509Certificate[] getAcceptedIssuers() { return this.trustManager.getAcceptedIssuers(); } } static class KeyManagerDelegate extends X509ExtendedKeyManager { private final X509ExtendedKeyManager keyManager; private final PrivateKeyStrategy aliasStrategy; KeyManagerDelegate(final X509ExtendedKeyManager keyManager, final PrivateKeyStrategy aliasStrategy) { this.keyManager = keyManager; this.aliasStrategy = aliasStrategy; } @Override public String[] getClientAliases( final String keyType, final Principal[] issuers) { return this.keyManager.getClientAliases(keyType, issuers); } public Map getClientAliasMap( final String[] keyTypes, final Principal[] issuers) { final Map validAliases = new HashMap<>(); for (final String keyType: keyTypes) { putPrivateKeyDetails(validAliases, keyType, this.keyManager.getClientAliases(keyType, issuers)); } return validAliases; } public Map getServerAliasMap( final String keyType, final Principal[] issuers) { final Map validAliases = new HashMap<>(); putPrivateKeyDetails(validAliases, keyType, this.keyManager.getServerAliases(keyType, issuers)); return validAliases; } private void putPrivateKeyDetails(final Map validAliases, final String keyType, final String[] aliases) { if (aliases != null) { for (final String alias: aliases) { validAliases.put(alias, new PrivateKeyDetails(keyType, this.keyManager.getCertificateChain(alias))); } } } @Override public String chooseClientAlias( final String[] keyTypes, final Principal[] issuers, final Socket socket) { final Map validAliases = getClientAliasMap(keyTypes, issuers); return this.aliasStrategy.chooseAlias(validAliases, socket instanceof SSLSocket ? ((SSLSocket) socket).getSSLParameters() : null); } @Override public String[] getServerAliases( final String keyType, final Principal[] issuers) { return this.keyManager.getServerAliases(keyType, issuers); } @Override public String chooseServerAlias( final String keyType, final Principal[] issuers, final Socket socket) { final Map validAliases = getServerAliasMap(keyType, issuers); return this.aliasStrategy.chooseAlias(validAliases, socket instanceof SSLSocket ? ((SSLSocket) socket).getSSLParameters() : null); } @Override public X509Certificate[] getCertificateChain(final String alias) { return this.keyManager.getCertificateChain(alias); } @Override public PrivateKey getPrivateKey(final String alias) { return this.keyManager.getPrivateKey(alias); } @Override public String chooseEngineClientAlias( final String[] keyTypes, final Principal[] issuers, final SSLEngine sslEngine) { final Map validAliases = getClientAliasMap(keyTypes, issuers); return this.aliasStrategy.chooseAlias(validAliases, sslEngine.getSSLParameters()); } @Override public String chooseEngineServerAlias( final String keyType, final Principal[] issuers, final SSLEngine sslEngine) { final Map validAliases = getServerAliasMap(keyType, issuers); return this.aliasStrategy.chooseAlias(validAliases, sslEngine.getSSLParameters()); } } /** * @throws NoSuchProviderException if no provider with the specified name is installed or if name is null. */ private Provider requireNonNullProvider(final String name) throws NoSuchProviderException { final Provider provider = Security.getProvider(name); if (provider == null) { throw new NoSuchProviderException(name); } return provider; } @Override public String toString() { return "[provider=" + provider + ", protocol=" + protocol + ", keyStoreType=" + keyStoreType + ", keyManagerFactoryAlgorithm=" + keyManagerFactoryAlgorithm + ", keyManagers=" + keyManagers + ", trustManagerFactoryAlgorithm=" + trustManagerFactoryAlgorithm + ", trustManagers=" + trustManagers + ", secureRandom=" + secureRandom + "]"; } } httpcore5/src/main/java/org/apache/hc/core5/ssl/SSLContexts.java0100644 0000000 0000000 00000007552 00000250600 023421 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.ssl; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import javax.net.ssl.SSLContext; /** * {@link javax.net.ssl.SSLContext} factory methods. * *

* Please note: the default Oracle JSSE implementation of * {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], java.security.SecureRandom) * SSLContext#init(KeyManager[], TrustManager[], SecureRandom)} * accepts multiple key and trust managers, however only only first matching type is ever used. * See for example: * * SSLContext.html#init * * @since 4.4 */ public final class SSLContexts { private SSLContexts() { // Do not allow utility class to be instantiated. } /** * Creates default factory based on the standard JSSE trust material * ({@code cacerts} file in the security properties directory). System properties * are not taken into consideration. * * @return the default SSL socket factory * @throws SSLInitializationException if NoSuchAlgorithmException or KeyManagementException * are thrown when invoking {@link SSLContext#getInstance(String)} */ public static SSLContext createDefault() throws SSLInitializationException { try { final SSLContext sslContext = SSLContext.getInstance(SSLContextBuilder.TLS); sslContext.init(null, null, null); return sslContext; } catch (final NoSuchAlgorithmException | KeyManagementException ex) { throw new SSLInitializationException(ex.getMessage(), ex); } } /** * Creates default SSL context based on system properties. This method obtains * default SSL context by calling {@code SSLContext.getInstance("Default")}. * Please note that {@code Default} algorithm is supported as of Java 6. * This method will fall back onto {@link #createDefault()} when * {@code Default} algorithm is not available. * * @return default system SSL context * @throws SSLInitializationException if {@link #createDefault()} throws it */ public static SSLContext createSystemDefault() throws SSLInitializationException { try { return SSLContext.getDefault(); } catch (final NoSuchAlgorithmException ex) { return createDefault(); } } /** * Creates custom SSL context. * * @return default system SSL context */ public static SSLContextBuilder custom() { return SSLContextBuilder.create(); } } httpcore5/src/main/java/org/apache/hc/core5/ssl/SSLInitializationException.java0100644 0000000 0000000 00000002721 00000250600 026451 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.ssl; public class SSLInitializationException extends IllegalStateException { private static final long serialVersionUID = -8243587425648536702L; public SSLInitializationException(final String message, final Throwable cause) { super(message, cause); } } httpcore5/src/main/java/org/apache/hc/core5/ssl/TrustStrategy.java0100644 0000000 0000000 00000006523 00000250600 024071 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.ssl; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; /** * A strategy to establish trustworthiness of certificates without consulting the trust manager * configured in the actual SSL context. This interface can be used to override the standard * JSSE certificate verification process. * *

Security Warning

* If a trust strategy considers a certificate chain to be trusted, then the default trust manager * will not be consulted. Trust strategy implementations should therefore consider properly checking * the complete certificate chain. Checking for example only the subject of a certificate does not * protect against man-in-the-middle attacks. For self-signed certificates prefer specifying a keystore * containing the certificate chain when calling the {@link SSLContextBuilder} {@code loadTrustMaterial} * methods instead of implementing a custom trust strategy. * *

A trust strategy alone cannot be used for certificate pinning. When {@code isTrusted} returns * {@code false} the certificate check falls back to the trust manager which might consider * the certificate trusted. See the {@link #isTrusted(X509Certificate[], String)} documentation. * * @see SSLContextBuilder * @since 4.4 */ public interface TrustStrategy { /** * Determines whether the certificate chain can be trusted without consulting the trust manager * configured in the actual SSL context. This method can be used to override the standard JSSE * certificate verification process. *

* Please note that, if this method returns {@code false}, the trust manager configured * in the actual SSL context can still clear the certificate as trusted. * * @param chain the peer certificate chain * @param authType the authentication type based on the client certificate * @return {@code true} if the certificate can be trusted without verification by * the trust manager, {@code false} otherwise. * @throws CertificateException thrown if the certificate is not trusted or invalid. */ boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException; } httpcore5/src/main/java/org/apache/hc/core5/ssl/package-info.java0100644 0000000 0000000 00000002452 00000250600 023546 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Utility classes for trust and key material management * and TLS/SSL context initialization. */ package org.apache.hc.core5.ssl; httpcore5/src/main/java/org/apache/hc/core5/util/0040755 0000000 0000000 00000000000 00000250600 020533 5ustar000000000 0000000 httpcore5/src/main/java/org/apache/hc/core5/util/Args.java0100644 0000000 0000000 00000020466 00000250600 022277 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import java.lang.reflect.Array; import java.util.Collection; import java.util.Map; import java.util.Objects; import org.apache.hc.core5.http.EntityDetails; public class Args { public static void check(final boolean expression, final String message) { if (!expression) { throw new IllegalArgumentException(message); } } public static void check(final boolean expression, final String message, final Object... args) { if (!expression) { throw new IllegalArgumentException(String.format(message, args)); } } public static void check(final boolean expression, final String message, final Object arg) { if (!expression) { throw new IllegalArgumentException(String.format(message, arg)); } } /** * @deprecated Use {@link #checkRange(long, long, long, String)}. */ @Deprecated public static long checkContentLength(final EntityDetails entityDetails) { // -1 is a special value, // 0 is allowed as well, // but never more than Integer.MAX_VALUE. return checkRange(entityDetails.getContentLength(), -1, Integer.MAX_VALUE, "HTTP entity too large to be buffered in memory)"); } public static int checkRange(final int value, final int lowInclusive, final int highInclusive, final String message) { if (value < lowInclusive || value > highInclusive) { throw illegalArgumentException("%s: %d is out of range [%d, %d]", message, value, lowInclusive, highInclusive); } return value; } public static long checkRange(final long value, final long lowInclusive, final long highInclusive, final String message) { if (value < lowInclusive || value > highInclusive) { throw illegalArgumentException("%s: %d is out of range [%d, %d]", message, value, lowInclusive, highInclusive); } return value; } public static T containsNoBlanks(final T argument, final String name) { notNull(argument, name); if (isEmpty(argument)) { throw illegalArgumentExceptionNotEmpty(name); } if (TextUtils.containsBlanks(argument)) { throw new IllegalArgumentException(name + " must not contain blanks"); } return argument; } private static IllegalArgumentException illegalArgumentException(final String format, final Object... args) { return new IllegalArgumentException(String.format(format, args)); } private static IllegalArgumentException illegalArgumentExceptionNotEmpty(final String name) { return new IllegalArgumentException(name + " must not be empty"); } public static T notBlank(final T argument, final String name) { notNull(argument, name); if (TextUtils.isBlank(argument)) { throw new IllegalArgumentException(name + " must not be blank"); } return argument; } public static T notEmpty(final T argument, final String name) { notNull(argument, name); if (isEmpty(argument)) { throw illegalArgumentExceptionNotEmpty(name); } return argument; } public static > T notEmpty(final T argument, final String name) { notNull(argument, name); if (isEmpty(argument)) { throw illegalArgumentExceptionNotEmpty(name); } return argument; } public static T notEmpty(final T argument, final String name) { notNull(argument, name); if (isEmpty(argument)) { throw illegalArgumentExceptionNotEmpty(name); } return argument; } public static int notNegative(final int n, final String name) { if (n < 0) { throw illegalArgumentException("%s must not be negative: %d", name, n); } return n; } public static long notNegative(final long n, final String name) { if (n < 0) { throw illegalArgumentException("%s must not be negative: %d", name, n); } return n; } /** *

Validate that the specified argument is not {@code null}; * otherwise throwing an exception with the specified message. * *

Args.notNull(myObject, "The object must not be null");
* * @param the object type * @param argument the object to check * @param name the {@link String} exception message if invalid, not null * @return the validated object (never {@code null} for method chaining) * @throws NullPointerException if the object is {@code null} */ public static T notNull(final T argument, final String name) { return Objects.requireNonNull(argument, name); } /** *

Checks if an Object is empty or null.

* * The following types are supported: *
    *
  • {@link CharSequence}: Considered empty if its length is zero.
  • *
  • {@code Array}: Considered empty if its length is zero.
  • *
  • {@link Collection}: Considered empty if it has zero elements.
  • *
  • {@link Map}: Considered empty if it has zero key-value mappings.
  • *
* *
     * Args.isEmpty(null)             = true
     * Args.isEmpty("")               = true
     * Args.isEmpty("ab")             = false
     * Args.isEmpty(new int[]{})      = true
     * Args.isEmpty(new int[]{1,2,3}) = false
     * Args.isEmpty(1234)             = false
     * 
* * @param object the {@code Object} to test, may be {@code null} * @return {@code true} if the object has a supported type and is empty or null, * {@code false} otherwise * @since 5.1 */ public static boolean isEmpty(final Object object) { if (object == null) { return true; } if (object instanceof CharSequence) { return ((CharSequence) object).length() == 0; } if (object.getClass().isArray()) { return Array.getLength(object) == 0; } if (object instanceof Collection) { return ((Collection) object).isEmpty(); } if (object instanceof Map) { return ((Map) object).isEmpty(); } return false; } public static int positive(final int n, final String name) { if (n <= 0) { throw illegalArgumentException("%s must not be negative or zero: %d", name, n); } return n; } public static long positive(final long n, final String name) { if (n <= 0) { throw illegalArgumentException("%s must not be negative or zero: %d", name, n); } return n; } public static T positive(final T timeValue, final String name) { positive(timeValue.getDuration(), name); return timeValue; } /** * Private constructor so that no instances can be created. This class * contains only static utility methods. */ private Args() { } } httpcore5/src/main/java/org/apache/hc/core5/util/Asserts.java0100644 0000000 0000000 00000004725 00000250600 023027 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; public class Asserts { private Asserts() { // Do not allow utility class to be instantiated. } public static void check(final boolean expression, final String message) { if (!expression) { throw new IllegalStateException(message); } } public static void check(final boolean expression, final String message, final Object... args) { if (!expression) { throw new IllegalStateException(String.format(message, args)); } } public static void check(final boolean expression, final String message, final Object arg) { if (!expression) { throw new IllegalStateException(String.format(message, arg)); } } public static void notNull(final Object object, final String name) { if (object == null) { throw new IllegalStateException(name + " is null"); } } public static void notEmpty(final CharSequence s, final String name) { if (TextUtils.isEmpty(s)) { throw new IllegalStateException(name + " is empty"); } } public static void notBlank(final CharSequence s, final String name) { if (TextUtils.isBlank(s)) { throw new IllegalStateException(name + " is blank"); } } } httpcore5/src/main/java/org/apache/hc/core5/util/ByteArrayBuffer.java0100644 0000000 0000000 00000027554 00000250600 024444 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import java.io.Serializable; import java.nio.ByteBuffer; /** * A resizable byte array. * * @since 4.0 */ public final class ByteArrayBuffer implements Serializable { private static final long serialVersionUID = 4359112959524048036L; private byte[] array; private int len; /** * Creates an instance of {@link ByteArrayBuffer} with the given initial * capacity. * * @param capacity the capacity */ public ByteArrayBuffer(final int capacity) { super(); Args.notNegative(capacity, "Buffer capacity"); this.array = new byte[capacity]; } private void expand(final int newlen) { final byte[] newArray = new byte[Math.max(this.array.length << 1, newlen)]; System.arraycopy(this.array, 0, newArray, 0, this.len); this.array = newArray; } /** * Appends {@code len} bytes to this buffer from the given source * array starting at index {@code off}. The capacity of the buffer * is increased, if necessary, to accommodate all {@code len} bytes. * * @param b the bytes to be appended. * @param off the index of the first byte to append. * @param len the number of bytes to append. * @throws IndexOutOfBoundsException if {@code off} if out of * range, {@code len} is negative, or * {@code off} + {@code len} is out of range. */ public void append(final byte[] b, final int off, final int len) { if (b == null) { return; } if (off < 0 || off > b.length || len < 0 || (off + len) < 0 || (off + len) > b.length) { throw new IndexOutOfBoundsException("off: " + off + " len: " + len + " b.length: " + b.length); } if (len == 0) { return; } final int newlen = this.len + len; if (newlen > this.array.length) { expand(newlen); } System.arraycopy(b, off, this.array, this.len, len); this.len = newlen; } /** * Appends {@code b} byte to this buffer. The capacity of the buffer * is increased, if necessary, to accommodate the additional byte. * * @param b the byte to be appended. */ public void append(final int b) { final int newlen = this.len + 1; if (newlen > this.array.length) { expand(newlen); } this.array[this.len] = (byte)b; this.len = newlen; } /** * Appends {@code len} chars to this buffer from the given source * array starting at index {@code off}. The capacity of the buffer * is increased if necessary to accommodate all {@code len} chars. *

* The chars are converted to bytes using simple cast. * * @param b the chars to be appended. * @param off the index of the first char to append. * @param len the number of bytes to append. * @throws IndexOutOfBoundsException if {@code off} if out of * range, {@code len} is negative, or * {@code off} + {@code len} is out of range. */ public void append(final char[] b, final int off, final int len) { if (b == null) { return; } if (off < 0 || off > b.length || len < 0 || (off + len) < 0 || (off + len) > b.length) { throw new IndexOutOfBoundsException("off: " + off + " len: " + len + " b.length: " + b.length); } if (len == 0) { return; } final int oldlen = this.len; final int newlen = oldlen + len; if (newlen > this.array.length) { expand(newlen); } for (int i1 = off, i2 = oldlen; i2 < newlen; i1++, i2++) { this.array[i2] = TextUtils.castAsByte(b[i1]); } this.len = newlen; } /** * Appends {@code len} chars to this buffer from the given source * char array buffer starting at index {@code off}. The capacity * of the buffer is increased if necessary to accommodate all * {@code len} chars. *

* The chars are converted to bytes using simple cast. * * @param b the chars to be appended. * @param off the index of the first char to append. * @param len the number of bytes to append. * @throws IndexOutOfBoundsException if {@code off} if out of * range, {@code len} is negative, or * {@code off} + {@code len} is out of range. */ public void append(final CharArrayBuffer b, final int off, final int len) { if (b == null) { return; } append(b.array(), off, len); } public void append(final ByteBuffer buffer) { if (buffer == null) { return; } final int bufferLength = buffer.remaining(); if (bufferLength > 0) { final int newLength = this.len + bufferLength; if (newLength > this.array.length) { expand(newLength); } buffer.get(this.array, this.len, bufferLength); this.len = newLength; } } /** * Clears content of the buffer. The underlying byte array is not resized. */ public void clear() { this.len = 0; } /** * Converts the content of this buffer to an array of bytes. * * @return byte array */ public byte[] toByteArray() { final byte[] b = new byte[this.len]; if (this.len > 0) { System.arraycopy(this.array, 0, b, 0, this.len); } return b; } /** * Returns the {@code byte} value in this buffer at the specified * index. The index argument must be greater than or equal to * {@code 0}, and less than the length of this buffer. * * @param i the index of the desired byte value. * @return the byte value at the specified index. * @throws IndexOutOfBoundsException if {@code index} is * negative or greater than or equal to {@link #length()}. */ public int byteAt(final int i) { return this.array[i]; } /** * Returns the current capacity. The capacity is the amount of storage * available for newly appended bytes, beyond which an allocation * will occur. * * @return the current capacity */ public int capacity() { return this.array.length; } /** * Returns the length of the buffer (byte count). * * @return the length of the buffer */ public int length() { return this.len; } /** * Ensures that the capacity is at least equal to the specified minimum. * If the current capacity is less than the argument, then a new internal * array is allocated with greater capacity. If the {@code required} * argument is non-positive, this method takes no action. * * @param required the minimum required capacity. * * @since 4.1 */ public void ensureCapacity(final int required) { if (required <= 0) { return; } final int available = this.array.length - this.len; if (required > available) { expand(this.len + required); } } /** * Returns reference to the underlying byte array. * * @return the byte array. */ public byte[] array() { return this.array; } /** * Sets the length of the buffer. The new length value is expected to be * less than the current capacity and greater than or equal to * {@code 0}. * * @param len the new length * @throws IndexOutOfBoundsException if the * {@code len} argument is greater than the current * capacity of the buffer or less than {@code 0}. */ public void setLength(final int len) { if (len < 0 || len > this.array.length) { throw new IndexOutOfBoundsException("len: " + len + " < 0 or > buffer len: " + this.array.length); } this.len = len; } /** * Returns {@code true} if this buffer is empty, that is, its * {@link #length()} is equal to {@code 0}. * @return {@code true} if this buffer is empty, {@code false} * otherwise. */ public boolean isEmpty() { return this.len == 0; } /** * Returns {@code true} if this buffer is full, that is, its * {@link #length()} is equal to its {@link #capacity()}. * @return {@code true} if this buffer is full, {@code false} * otherwise. */ public boolean isFull() { return this.len == this.array.length; } /** * Returns the index within this buffer of the first occurrence of the * specified byte, starting the search at the specified * {@code beginIndex} and finishing at {@code endIndex}. * If no such byte occurs in this buffer within the specified bounds, * {@code -1} is returned. *

* There is no restriction on the value of {@code beginIndex} and * {@code endIndex}. If {@code beginIndex} is negative, * it has the same effect as if it were zero. If {@code endIndex} is * greater than {@link #length()}, it has the same effect as if it were * {@link #length()}. If the {@code beginIndex} is greater than * the {@code endIndex}, {@code -1} is returned. * * @param b the byte to search for. * @param from the index to start the search from. * @param to the index to finish the search at. * @return the index of the first occurrence of the byte in the buffer * within the given bounds, or {@code -1} if the byte does * not occur. * * @since 4.1 */ public int indexOf(final byte b, final int from, final int to) { int beginIndex = from; if (beginIndex < 0) { beginIndex = 0; } int endIndex = to; if (endIndex > this.len) { endIndex = this.len; } if (beginIndex > endIndex) { return -1; } for (int i = beginIndex; i < endIndex; i++) { if (this.array[i] == b) { return i; } } return -1; } /** * Returns the index within this buffer of the first occurrence of the * specified byte, starting the search at {@code 0} and finishing * at {@link #length()}. If no such byte occurs in this buffer within * those bounds, {@code -1} is returned. * * @param b the byte to search for. * @return the index of the first occurrence of the byte in the * buffer, or {@code -1} if the byte does not occur. * * @since 4.1 */ public int indexOf(final byte b) { return indexOf(b, 0, this.len); } } httpcore5/src/main/java/org/apache/hc/core5/util/CharArrayBuffer.java0100644 0000000 0000000 00000042275 00000250600 024413 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import java.io.Serializable; import java.nio.CharBuffer; import org.apache.hc.core5.http.Chars; /** * A resizable char array. * * @since 4.0 */ public final class CharArrayBuffer implements CharSequence, Serializable { private static final long serialVersionUID = -6208952725094867135L; private char[] array; private int len; /** * Creates an instance of {@link CharArrayBuffer} with the given initial * capacity. * * @param capacity the capacity */ public CharArrayBuffer(final int capacity) { super(); Args.notNegative(capacity, "Buffer capacity"); this.array = new char[capacity]; } private void expand(final int newlen) { final char[] newArray = new char[Math.max(this.array.length << 1, newlen)]; System.arraycopy(this.array, 0, newArray, 0, this.len); this.array = newArray; } /** * Appends {@code len} chars to this buffer from the given source * array starting at index {@code off}. The capacity of the buffer * is increased, if necessary, to accommodate all {@code len} chars. * * @param b the chars to be appended. * @param off the index of the first char to append. * @param len the number of chars to append. * @throws IndexOutOfBoundsException if {@code off} is out of * range, {@code len} is negative, or * {@code off} + {@code len} is out of range. */ public void append(final char[] b, final int off, final int len) { if (b == null) { return; } if (off < 0 || off > b.length || len < 0 || (off + len) < 0 || (off + len) > b.length) { throw new IndexOutOfBoundsException("off: " + off + " len: " + len + " b.length: " + b.length); } if (len == 0) { return; } final int newlen = this.len + len; if (newlen > this.array.length) { expand(newlen); } System.arraycopy(b, off, this.array, this.len, len); this.len = newlen; } /** * Appends chars of the given string to this buffer. The capacity of the * buffer is increased, if necessary, to accommodate all chars. * * @param str the string. */ public void append(final String str) { final String s = str != null ? str : "null"; final int strlen = s.length(); final int newlen = this.len + strlen; if (newlen > this.array.length) { expand(newlen); } s.getChars(0, strlen, this.array, this.len); this.len = newlen; } /** * Appends {@code len} chars to this buffer from the given source * buffer starting at index {@code off}. The capacity of the * destination buffer is increased, if necessary, to accommodate all * {@code len} chars. * * @param b the source buffer to be appended. * @param off the index of the first char to append. * @param len the number of chars to append. * @throws IndexOutOfBoundsException if {@code off} is out of * range, {@code len} is negative, or * {@code off} + {@code len} is out of range. */ public void append(final CharArrayBuffer b, final int off, final int len) { if (b == null) { return; } append(b.array, off, len); } /** * Appends all chars to this buffer from the given source buffer starting * at index {@code 0}. The capacity of the destination buffer is * increased, if necessary, to accommodate all {@link #length()} chars. * * @param b the source buffer to be appended. */ public void append(final CharArrayBuffer b) { if (b == null) { return; } append(b.array,0, b.len); } /** * Appends {@code ch} char to this buffer. The capacity of the buffer * is increased, if necessary, to accommodate the additional char. * * @param ch the char to be appended. */ public void append(final char ch) { final int newlen = this.len + 1; if (newlen > this.array.length) { expand(newlen); } this.array[this.len] = ch; this.len = newlen; } /** * Appends {@code len} bytes to this buffer from the given source * array starting at index {@code off}. The capacity of the buffer * is increased, if necessary, to accommodate all {@code len} bytes. *

* The bytes are converted to chars using simple cast. * * @param b the bytes to be appended. * @param off the index of the first byte to append. * @param len the number of bytes to append. * @throws IndexOutOfBoundsException if {@code off} is out of * range, {@code len} is negative, or * {@code off} + {@code len} is out of range. */ public void append(final byte[] b, final int off, final int len) { if (b == null) { return; } if (off < 0 || off > b.length || len < 0 || (off + len) < 0 || (off + len) > b.length) { throw new IndexOutOfBoundsException("off: " + off + " len: " + len + " b.length: " + b.length); } if (len == 0) { return; } final int oldlen = this.len; final int newlen = oldlen + len; if (newlen > this.array.length) { expand(newlen); } for (int i1 = off, i2 = oldlen; i2 < newlen; i1++, i2++) { this.array[i2] = (char) (b[i1] & 0xff); } this.len = newlen; } /** * Appends {@code len} bytes to this buffer from the given source * array starting at index {@code off}. The capacity of the buffer * is increased, if necessary, to accommodate all {@code len} bytes. *

* The bytes are converted to chars using simple cast. * * @param b the bytes to be appended. * @param off the index of the first byte to append. * @param len the number of bytes to append. * @throws IndexOutOfBoundsException if {@code off} is out of * range, {@code len} is negative, or * {@code off} + {@code len} is out of range. */ public void append(final ByteArrayBuffer b, final int off, final int len) { if (b == null) { return; } append(b.array(), off, len); } /** * Appends chars of the textual representation of the given object to this * buffer. The capacity of the buffer is increased, if necessary, to * accommodate all chars. * * @param obj the object. */ public void append(final Object obj) { append(String.valueOf(obj)); } /** * Clears content of the buffer. The underlying char array is not resized. */ public void clear() { this.len = 0; } /** * Converts the content of this buffer to an array of chars. * * @return char array */ public char[] toCharArray() { final char[] b = new char[this.len]; if (this.len > 0) { System.arraycopy(this.array, 0, b, 0, this.len); } return b; } /** * Returns the {@code char} value in this buffer at the specified * index. The index argument must be greater than or equal to * {@code 0}, and less than the length of this buffer. * * @param i the index of the desired char value. * @return the char value at the specified index. * @throws IndexOutOfBoundsException if {@code index} is * negative or greater than or equal to {@link #length()}. */ @Override public char charAt(final int i) { return this.array[i]; } /** * Returns reference to the underlying char array. * * @return the char array. */ public char[] array() { return this.array; } /** * Returns the current capacity. The capacity is the amount of storage * available for newly appended chars, beyond which an allocation will * occur. * * @return the current capacity */ public int capacity() { return this.array.length; } /** * Returns the length of the buffer (char count). * * @return the length of the buffer */ @Override public int length() { return this.len; } /** * Ensures that the capacity is at least equal to the specified minimum. * If the current capacity is less than the argument, then a new internal * array is allocated with greater capacity. If the {@code required} * argument is non-positive, this method takes no action. * * @param required the minimum required capacity. */ public void ensureCapacity(final int required) { if (required <= 0) { return; } final int available = this.array.length - this.len; if (required > available) { expand(this.len + required); } } /** * Sets the length of the buffer. The new length value is expected to be * less than the current capacity and greater than or equal to * {@code 0}. * * @param len the new length * @throws IndexOutOfBoundsException if the * {@code len} argument is greater than the current * capacity of the buffer or less than {@code 0}. */ public void setLength(final int len) { if (len < 0 || len > this.array.length) { throw new IndexOutOfBoundsException("len: " + len + " < 0 or > buffer len: " + this.array.length); } this.len = len; } /** * Returns {@code true} if this buffer is empty, that is, its * {@link #length()} is equal to {@code 0}. * @return {@code true} if this buffer is empty, {@code false} * otherwise. */ public boolean isEmpty() { return this.len == 0; } /** * Returns {@code true} if this buffer is full, that is, its * {@link #length()} is equal to its {@link #capacity()}. * @return {@code true} if this buffer is full, {@code false} * otherwise. */ public boolean isFull() { return this.len == this.array.length; } /** * Returns the index within this buffer of the first occurrence of the * specified character, starting the search at the specified * {@code beginIndex} and finishing at {@code endIndex}. * If no such character occurs in this buffer within the specified bounds, * {@code -1} is returned. *

* There is no restriction on the value of {@code beginIndex} and * {@code endIndex}. If {@code beginIndex} is negative, * it has the same effect as if it were zero. If {@code endIndex} is * greater than {@link #length()}, it has the same effect as if it were * {@link #length()}. If the {@code beginIndex} is greater than * the {@code endIndex}, {@code -1} is returned. * * @param ch the char to search for. * @param from the index to start the search from. * @param to the index to finish the search at. * @return the index of the first occurrence of the character in the buffer * within the given bounds, or {@code -1} if the character does * not occur. */ public int indexOf(final int ch, final int from, final int to) { int beginIndex = from; if (beginIndex < 0) { beginIndex = 0; } int endIndex = to; if (endIndex > this.len) { endIndex = this.len; } if (beginIndex > endIndex) { return -1; } for (int i = beginIndex; i < endIndex; i++) { if (this.array[i] == ch) { return i; } } return -1; } /** * Returns the index within this buffer of the first occurrence of the * specified character, starting the search at {@code 0} and finishing * at {@link #length()}. If no such character occurs in this buffer within * those bounds, {@code -1} is returned. * * @param ch the char to search for. * @return the index of the first occurrence of the character in the * buffer, or {@code -1} if the character does not occur. */ public int indexOf(final int ch) { return indexOf(ch, 0, this.len); } /** * Returns a substring of this buffer. The substring begins at the specified * {@code beginIndex} and extends to the character at index * {@code endIndex - 1}. * * @param beginIndex the beginning index, inclusive. * @param endIndex the ending index, exclusive. * @return the specified substring. * @throws StringIndexOutOfBoundsException if the * {@code beginIndex} is negative, or * {@code endIndex} is larger than the length of this * buffer, or {@code beginIndex} is larger than * {@code endIndex}. */ public String substring(final int beginIndex, final int endIndex) { if (beginIndex < 0) { throw new IndexOutOfBoundsException("Negative beginIndex: " + beginIndex); } if (endIndex > this.len) { throw new IndexOutOfBoundsException("endIndex: " + endIndex + " > length: " + this.len); } if (beginIndex > endIndex) { throw new IndexOutOfBoundsException("beginIndex: " + beginIndex + " > endIndex: " + endIndex); } return new String(this.array, beginIndex, endIndex - beginIndex); } private static boolean isWhitespace(final char ch) { return ch == Chars.SP || ch == Chars.HT || ch == Chars.CR || ch == Chars.LF; } /** * Returns a substring of this buffer with leading and trailing whitespace * omitted. The substring begins with the first non-whitespace character * from {@code beginIndex} and extends to the last * non-whitespace character with the index lesser than * {@code endIndex}. * * @param beginIndex the beginning index, inclusive. * @param endIndex the ending index, exclusive. * @return the specified substring. * @throws IndexOutOfBoundsException if the * {@code beginIndex} is negative, or * {@code endIndex} is larger than the length of this * buffer, or {@code beginIndex} is larger than * {@code endIndex}. */ public String substringTrimmed(final int beginIndex, final int endIndex) { if (beginIndex < 0) { throw new IndexOutOfBoundsException("Negative beginIndex: " + beginIndex); } if (endIndex > this.len) { throw new IndexOutOfBoundsException("endIndex: " + endIndex + " > length: " + this.len); } if (beginIndex > endIndex) { throw new IndexOutOfBoundsException("beginIndex: " + beginIndex + " > endIndex: " + endIndex); } int beginIndex0 = beginIndex; int endIndex0 = endIndex; while (beginIndex0 < endIndex && isWhitespace(this.array[beginIndex0])) { beginIndex0++; } while (endIndex0 > beginIndex0 && isWhitespace(this.array[endIndex0 - 1])) { endIndex0--; } return new String(this.array, beginIndex0, endIndex0 - beginIndex0); } /** * {@inheritDoc} * @since 4.4 */ @Override public CharSequence subSequence(final int beginIndex, final int endIndex) { if (beginIndex < 0) { throw new IndexOutOfBoundsException("Negative beginIndex: " + beginIndex); } if (endIndex > this.len) { throw new IndexOutOfBoundsException("endIndex: " + endIndex + " > length: " + this.len); } if (beginIndex > endIndex) { throw new IndexOutOfBoundsException("beginIndex: " + beginIndex + " > endIndex: " + endIndex); } return CharBuffer.wrap(this.array, beginIndex, endIndex - beginIndex); } @Override public String toString() { return new String(this.array, 0, this.len); } } httpcore5/src/main/java/org/apache/hc/core5/util/Deadline.java0100644 0000000 0000000 00000022350 00000250600 023102 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import java.text.ParseException; import java.time.Instant; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.util.concurrent.TimeUnit; /** * A deadline based on a UNIX time, the elapsed since 00:00:00 Coordinated Universal Time (UTC), Thursday, 1 January * 1970. * * @since 5.0 */ public class Deadline { /** * The format used for parsing and formatting dates. */ public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; /** * A special internal value that marks a deadline as the longest possible. */ private static final long INTERNAL_MAX_VALUE = Long.MAX_VALUE; /** * A special internal value that marks a deadline as the shortest possible. */ private static final long INTERNAL_MIN_VALUE = 0; /** * The maximum (longest-lived) deadline. */ public static Deadline MAX_VALUE = new Deadline(INTERNAL_MAX_VALUE); /** * The minimum (shortest-lived) deadline. */ public static Deadline MIN_VALUE = new Deadline(INTERNAL_MIN_VALUE); private static final DateTimeFormatter DATE_TIME_FORMATTER = new DateTimeFormatterBuilder() .parseLenient() .parseCaseInsensitive() .appendPattern(DATE_FORMAT) .toFormatter(); /** * Calculates a deadline with a given time in milliseconds plus a given time value. Non-positive time values * represent an indefinite timeout without a deadline. * * @param timeMillis A time in UNIX milliseconds, usually the current time. * @param timeValue time value to add to {@code timeMillis}. * @return a deadline representing the current time plus the given time value. */ public static Deadline calculate(final long timeMillis, final TimeValue timeValue) { if (TimeValue.isPositive(timeValue)) { return Deadline.fromUnixMilliseconds(timeMillis + Math.min(timeValue.toMilliseconds(), Long.MAX_VALUE - timeMillis)); } return Deadline.MAX_VALUE; } /** * Calculates a deadline from now plus a given time value. Non-positive time values * represent an indefinite timeout without a deadline. * * @param timeValue time value to add to {@code timeMillis}. * @return a deadline representing the current time plus the given time value. */ public static Deadline calculate(final TimeValue timeValue) { return calculate(System.currentTimeMillis(), timeValue); } /** * Creates a deadline from a UNIX time in milliseconds. * * @param value a UNIX time in milliseconds. * @return a new deadline. */ public static Deadline fromUnixMilliseconds(final long value) { if (value == INTERNAL_MAX_VALUE) { return MAX_VALUE; } if (value == INTERNAL_MIN_VALUE) { return MIN_VALUE; } return new Deadline(value); } /** * Creates a deadline from a string in the format {@value #DATE_FORMAT}. * * @param source a string in the format {@value #DATE_FORMAT}. * @return a deadline from a string in the format {@value #DATE_FORMAT}. * @throws ParseException if the specified source string cannot be parsed. */ public static Deadline parse(final String source) throws ParseException { if (source == null) { return null; } final Instant instant = Instant.from(DATE_TIME_FORMATTER.parse(source)); return fromUnixMilliseconds(instant.toEpochMilli()); } private volatile boolean frozen; private volatile long lastCheck; /* * Internal representation is a UNIX time. */ private final long value; /** * Constructs a new instance with the given UNIX time in milliseconds. * * @param deadlineMillis UNIX time in milliseconds. */ private Deadline(final long deadlineMillis) { super(); this.value = deadlineMillis; setLastCheck(); } @Override public boolean equals(final Object obj) { // Only take into account the deadline value. if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Deadline other = (Deadline) obj; return value == other.value; } @Override public int hashCode() { // Only take into account the deadline value. return Long.hashCode(value); } /** * Formats this deadline. * * @param overdueTimeUnit the time unit to show how much over the deadline we are. * @return a formatted string. */ public String format(final TimeUnit overdueTimeUnit) { if (value == MAX_VALUE.value) { return "No deadline (infinite)"; } return String.format("Deadline: %s, %s overdue", formatTarget(), TimeValue.of(remaining(), overdueTimeUnit)); } /** * Formats the deadline value as a string in the format {@value #DATE_FORMAT}. * * @return a formatted string in the format {@value #DATE_FORMAT}. */ public String formatTarget() { if (value == MAX_VALUE.value) { return "(infinite)"; } return DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(value).atOffset(ZoneOffset.UTC)); } public Deadline freeze() { frozen = true; return this; } /** * Package private for testing. * * @return the last time we checked the current time. */ long getLastCheck() { return lastCheck; } /** * Gets the UNIX time deadline value. * * @return the UNIX time deadline value. */ public long getValue() { return value; } /** * Returns whether this deadline occurs before the given time in milliseconds. * * @param millis the time to compare. * @return whether this deadline occurs before the given time in milliseconds. */ public boolean isBefore(final long millis) { return value < millis; } /** * Returns whether the deadline has expired. * * @return whether the deadline has expired. */ public boolean isExpired() { setLastCheck(); return value < this.lastCheck; } /** * Returns whether this deadline is the maximum deadline. * * @return whether this deadline is the maximum deadline. */ public boolean isMax() { return value == INTERNAL_MAX_VALUE; } /** * Returns whether this deadline is the minimum deadline. * * @return whether this deadline is the minimum deadline. */ public boolean isMin() { return value == INTERNAL_MIN_VALUE; } /** * Returns whether this deadline has not expired. * * @return whether this deadline has not expired. */ public boolean isNotExpired() { setLastCheck(); return value >= this.lastCheck; } /** * Returns the smaller of this and another {@code Deadline}. * * @param other another deadline. * @return the smaller of {@code this} and {@code other}. */ public Deadline min(final Deadline other) { return value <= other.value ? this : other; } /** * Returns the difference in milliseconds between the deadline and now. * * @return the different in milliseconds between the deadline and now. */ public long remaining() { setLastCheck(); return value - lastCheck; } /** * Returns the difference as a TimeValue between the deadline and now. * * @return Returns the different as a TimeValue between the deadline and now. */ public TimeValue remainingTimeValue() { return TimeValue.of(remaining(), TimeUnit.MILLISECONDS); } private void setLastCheck() { if (!frozen) { this.lastCheck = System.currentTimeMillis(); } } @Override public String toString() { return formatTarget(); } } httpcore5/src/main/java/org/apache/hc/core5/util/DeadlineTimeoutException.java0100644 0000000 0000000 00000005263 00000250600 026334 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * A specialization of {@link TimeoutException} that carries a deadline and an actual value, both as UNIX times. * * @since 5.0 */ public class DeadlineTimeoutException extends TimeoutException { private static final long serialVersionUID = 1L; /** * Creates a new exception for the given timeout deadline and actual timeout. * * @param deadline When was the deadline in UNIX time. * @return a new TimeoutValueException. */ public static DeadlineTimeoutException from(final Deadline deadline) { return new DeadlineTimeoutException(deadline); } private final Deadline deadline; /** * Creates a new exception for the given timeout deadline and actual timeout. * * @param deadline When was the deadline in UNIX time. */ private DeadlineTimeoutException(final Deadline deadline) { // Deadline in a format like ISO8601: YYYY-MM-DDThh:mm:ss.sTZD // We use the TimeZone zone ID because we cannot get from String.format() in // Java 7 and the short names have been deprecated. super(deadline.format(TimeUnit.MILLISECONDS)); this.deadline = deadline; } /** * The expected deadline for this timeout since the start of UNIX time. * * @return The expected deadline for this timeout since the start of UNIX time. */ public Deadline getDeadline() { return deadline; } } httpcore5/src/main/java/org/apache/hc/core5/util/Identifiable.java0100644 0000000 0000000 00000002471 00000250600 023756 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; /** * Object with a unique identifier. * * @since 5.0 */ public interface Identifiable { String getId(); } httpcore5/src/main/java/org/apache/hc/core5/util/LangUtils.java0100644 0000000 0000000 00000006303 00000250600 023277 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import java.util.Arrays; import java.util.Objects; /** * A set of utility methods to help produce consistent * {@link Object#equals equals} and {@link Object#hashCode hashCode} methods. * * * @since 4.0 */ public final class LangUtils { public static final int HASH_SEED = 17; public static final int HASH_OFFSET = 37; /** Disabled default constructor. */ private LangUtils() { } public static int hashCode(final int seed, final int hashcode) { return seed * HASH_OFFSET + hashcode; } public static int hashCode(final int seed, final boolean b) { return hashCode(seed, b ? 1 : 0); } public static int hashCode(final int seed, final Object obj) { return hashCode(seed, obj != null ? obj.hashCode() : 0); } /** * Check if two objects are equal. * * @param obj1 first object to compare, may be {@code null} * @param obj2 second object to compare, may be {@code null} * @return {@code true} if the objects are equal or both null * @deprecated Use {@link Objects#equals(Object)}. */ @Deprecated public static boolean equals(final Object obj1, final Object obj2) { return Objects.equals(obj1, obj2); } /** * Check if two object arrays are equal. *

    *
  • If both parameters are null, return {@code true}
  • *
  • If one parameter is null, return {@code false}
  • *
  • If the array lengths are different, return {@code false}
  • *
  • Compare array elements using .equals(); return {@code false} if any comparisons fail.
  • *
  • Return {@code true}
  • *
* * @param a1 first array to compare, may be {@code null} * @param a2 second array to compare, may be {@code null} * @return {@code true} if the arrays are equal or both null * @deprecated Use {@link Arrays#equals(Object)}. */ @Deprecated public static boolean equals(final Object[] a1, final Object[] a2) { return Arrays.equals(a1, a2); } } httpcore5/src/main/java/org/apache/hc/core5/util/ReflectionUtils.java0100644 0000000 0000000 00000007610 00000250600 024512 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import jdk.net.ExtendedSocketOptions; import jdk.net.Sockets; import org.apache.hc.core5.annotation.Internal; import java.lang.reflect.Method; import java.net.Socket; import java.util.Arrays; @Internal @SuppressWarnings("Since15") public final class ReflectionUtils { private static final boolean SUPPORTS_KEEPALIVE_OPTIONS = Sockets.supportedOptions(Socket.class) .containsAll(Arrays.asList(ExtendedSocketOptions.TCP_KEEPIDLE, ExtendedSocketOptions.TCP_KEEPINTERVAL, ExtendedSocketOptions.TCP_KEEPCOUNT)); public static void callSetter(final Object object, final String setterName, final Class type, final Object value) { try { final Class clazz = object.getClass(); final Method method = clazz.getMethod("set" + setterName, type); method.setAccessible(true); method.invoke(object, value); } catch (final Exception ignore) { } } public static T callGetter(final Object object, final String getterName, final Class resultType) { return callGetter(object, getterName, null, null, resultType); } /** * @param The return type. * @since 5.3 */ public static T callGetter(final Object object, final String getterName, final Object arg, final Class argType, final Class resultType) { try { final Class clazz = object.getClass(); final Method method; if (arg != null) { assert argType != null; method = clazz.getMethod("get" + getterName, argType); method.setAccessible(true); return resultType.cast(method.invoke(object, arg)); } else { assert argType == null; method = clazz.getMethod("get" + getterName); method.setAccessible(true); return resultType.cast(method.invoke(object)); } } catch (final Exception ignore) { return null; } } public static int determineJRELevel() { final String s = System.getProperty("java.version"); final String[] parts = s.split("\\."); if (parts.length > 0) { try { final int majorVersion = Integer.parseInt(parts[0]); if (majorVersion > 1) { return majorVersion; } else if (majorVersion == 1 && parts.length > 1) { return Integer.parseInt(parts[1]); } } catch (final NumberFormatException ignore) { } } return 7; } public static boolean supportsKeepAliveOptions() { return SUPPORTS_KEEPALIVE_OPTIONS; } } httpcore5/src/main/java/org/apache/hc/core5/util/TextUtils.java0100644 0000000 0000000 00000013452 00000250600 023345 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import java.util.Locale; import org.apache.hc.core5.annotation.Internal; /** * Tests and converts Strings and CharSequence. * * @since 4.3 */ public final class TextUtils { private TextUtils() { // Do not allow utility class to be instantiated. } /** * Tests whether the parameter is null or of zero length. * * @param s The CharSequence to test. * @return whether the parameter is null or of zero length. */ public static boolean isEmpty(final CharSequence s) { return length(s) == 0; } /** *

Checks if a CharSequence is empty (""), null or whitespace only.

* *

Whitespace is defined by {@link Character#isWhitespace(char)}.

* *
     * TextUtils.isBlank(null)      = true
     * TextUtils.isBlank("")        = true
     * TextUtils.isBlank(" ")       = true
     * TextUtils.isBlank("abg")     = false
     * TextUtils.isBlank("  abg  ") = false
     * 
* * @param s the CharSequence to check, may be null * @return {@code true} if the CharSequence is null, empty or whitespace only */ public static boolean isBlank(final CharSequence s) { final int strLen = length(s); if (strLen == 0) { return true; } for (int i = 0; i < strLen; i++) { if (!Character.isWhitespace(s.charAt(i))) { return false; } } return true; } /** * Gets a CharSequence length or {@code 0} if the CharSequence is * {@code null}. * * @param cs * a CharSequence or {@code null} * @return CharSequence length or {@code 0} if the CharSequence is * {@code null}. * @since 5.1 */ public static int length(final CharSequence cs) { return cs == null ? 0 : cs.length(); } /** * Tests whether a CharSequence contains any whitespace. * * @param s The CharSequence to test. * @return whether a CharSequence contains any whitespace. * @since 4.4 */ public static boolean containsBlanks(final CharSequence s) { final int strLen = length(s); if (strLen == 0) { return false; } for (int i = 0; i < strLen; i++) { if (Character.isWhitespace(s.charAt(i))) { return true; } } return false; } /** * Returns a hexadecimal string with lowercase letters, representing the * values of the {@code bytes}. * * @param bytes whose hex string should be created * @return hex string for the bytes * * @since 5.0 */ private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; public static String toHexString(final byte[] bytes) { if (bytes == null) { return null; } final StringBuilder buffer = new StringBuilder(bytes.length * 2); for (final byte b : bytes) { buffer.append(HEX_CHARS[(0xf0 & b) >>> 4]).append(HEX_CHARS[0x0f & b]); } return buffer.toString(); } /** * Converts a String to its lower case representation using {@link Locale#ROOT}. * * @param s The String to convert * @return The converted String. * @since 5.2 */ public static String toLowerCase(final String s) { if (s == null) { return null; } return s.toLowerCase(Locale.ROOT); } /** * Determines whether the given {@link CharSequence} contains only ASCII characters. * * @param s the {@link CharSequence} to check * @return true if the {@link CharSequence} contains only ASCII characters, false otherwise * @throws IllegalArgumentException if the input {@link CharSequence} is null * @since 5.3 */ public static boolean isAllASCII(final CharSequence s) { final int strLen = length(s); for (int i = 0; i < strLen; i++) { if (s.charAt(i) > 0x7F) { return false; } } return true; } /** * Casts character to byte filtering non-visible and non-ASCII characters before conversion. * * @param c The character to cast. * @return The given character or {@code '?'}. * @since 5.2 */ @Internal public static byte castAsByte(final int c) { if (c >= 0x20 && c <= 0x7E || // Visible ASCII c >= 0xA0 && c <= 0xFF || // Visible ISO-8859-1 c == 0x09) { // TAB return (byte) c; } return '?'; } } httpcore5/src/main/java/org/apache/hc/core5/util/TimeValue.java0100644 0000000 0000000 00000033675 00000250600 023304 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import java.text.ParseException; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.Locale; import java.util.Objects; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; /** * Represents a time value as a {@code long} time and a {@link TimeUnit}. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class TimeValue implements Comparable { static final int INT_UNDEFINED = -1; /** * A constant holding the maximum value a {@code TimeValue} can have: {@code Long.MAX_VALUE} days. */ public static final TimeValue MAX_VALUE = ofDays(Long.MAX_VALUE); /** * A negative one millisecond {@link TimeValue}. */ public static final TimeValue NEG_ONE_MILLISECOND = TimeValue.of(INT_UNDEFINED, TimeUnit.MILLISECONDS); /** * A negative one second {@link TimeValue}. */ public static final TimeValue NEG_ONE_SECOND = TimeValue.of(INT_UNDEFINED, TimeUnit.SECONDS); /** * A zero milliseconds {@link TimeValue}. */ public static final TimeValue ZERO_MILLISECONDS = TimeValue.of(0, TimeUnit.MILLISECONDS); /** * Returns the given {@code long} value as an {@code int} where long values out of int range are returned as * {@link Integer#MIN_VALUE} and {@link Integer#MAX_VALUE}. * *

* For example: {@code TimeValue.asBoundInt(Long.MAX_VALUE)} returns {@code Integer.MAX_VALUE}. *

* * @param value a long value to convert * @return an int value bound within {@link Integer#MIN_VALUE} and {@link Integer#MAX_VALUE}. */ public static int asBoundInt(final long value) { if (value > Integer.MAX_VALUE) { return Integer.MAX_VALUE; } else if (value < Integer.MIN_VALUE) { return Integer.MIN_VALUE; } return (int) value; } /** * Returns the given {@code timeValue} if it is not {@code null}, if {@code null} then returns the given * {@code defaultValue}. * * @param The type of {@link TimeValue}. * @param timeValue may be {@code null} * @param defaultValue may be {@code null} * @return {@code timeValue} or {@code defaultValue} */ public static T defaultsTo(final T timeValue, final T defaultValue) { return timeValue != null ? timeValue : defaultValue; } /** * Returns the given {@code timeValue} if it is not {@code null}, if {@code null} then returns * {@link #NEG_ONE_SECOND}. * * @param timeValue may be {@code null} * @return {@code timeValue} or {@link #NEG_ONE_SECOND} */ public static TimeValue defaultsToNegativeOneMillisecond(final TimeValue timeValue) { return defaultsTo(timeValue, NEG_ONE_MILLISECOND); } /** * Returns the given {@code timeValue} if it is not {@code null}, if {@code null} then returns * {@link #NEG_ONE_SECOND}. * * @param timeValue may be {@code null} * @return {@code timeValue} or {@link #NEG_ONE_SECOND} */ public static TimeValue defaultsToNegativeOneSecond(final TimeValue timeValue) { return defaultsTo(timeValue, NEG_ONE_SECOND); } /** * Returns the given {@code timeValue} if it is not {@code null}, if {@code null} then returns * {@link #ZERO_MILLISECONDS}. * * @param timeValue may be {@code null} * @return {@code timeValue} or {@link #ZERO_MILLISECONDS} */ public static TimeValue defaultsToZeroMilliseconds(final TimeValue timeValue) { return defaultsTo(timeValue, ZERO_MILLISECONDS); } public static boolean isNonNegative(final TimeValue timeValue) { return timeValue != null && timeValue.getDuration() >= 0; } public static boolean isPositive(final TimeValue timeValue) { return timeValue != null && timeValue.getDuration() > 0; } /** * Creates a TimeValue. * * @param duration the time duration in the given {@code timeUnit}. * @param timeUnit the time unit for the given duration. * @return a Timeout. */ public static TimeValue of(final long duration, final TimeUnit timeUnit) { return new TimeValue(duration, timeUnit); } /** * Creates a TimeValue from a Duration. * * @param duration the time duration. * @return a Timeout * @since 5.2 */ public static TimeValue of(final Duration duration) { final long seconds = duration.getSeconds(); final long nanoOfSecond = duration.getNano(); if (seconds == 0) { // no conversion return of(nanoOfSecond, TimeUnit.NANOSECONDS); } else if (nanoOfSecond == 0) { // no conversion return of(seconds, TimeUnit.SECONDS); } // conversion attempts try { return of(duration.toNanos(), TimeUnit.NANOSECONDS); } catch (final ArithmeticException e) { try { return of(duration.toMillis(), TimeUnit.MILLISECONDS); } catch (final ArithmeticException e1) { // backstop return of(seconds, TimeUnit.SECONDS); } } } public static TimeValue ofDays(final long days) { return of(days, TimeUnit.DAYS); } public static TimeValue ofHours(final long hours) { return of(hours, TimeUnit.HOURS); } public static TimeValue ofMicroseconds(final long microseconds) { return of(microseconds, TimeUnit.MICROSECONDS); } public static TimeValue ofMilliseconds(final long millis) { return of(millis, TimeUnit.MILLISECONDS); } public static TimeValue ofMinutes(final long minutes) { return of(minutes, TimeUnit.MINUTES); } public static TimeValue ofNanoseconds(final long nanoseconds) { return of(nanoseconds, TimeUnit.NANOSECONDS); } public static TimeValue ofSeconds(final long seconds) { return of(seconds, TimeUnit.SECONDS); } /** * Converts a {@link TimeUnit} to the equivalent {@link ChronoUnit}. * * @return the converted equivalent ChronoUnit */ static ChronoUnit toChronoUnit(final TimeUnit timeUnit) { switch (Objects.requireNonNull(timeUnit)) { case NANOSECONDS: return ChronoUnit.NANOS; case MICROSECONDS: return ChronoUnit.MICROS; case MILLISECONDS: return ChronoUnit.MILLIS; case SECONDS: return ChronoUnit.SECONDS; case MINUTES: return ChronoUnit.MINUTES; case HOURS: return ChronoUnit.HOURS; case DAYS: return ChronoUnit.DAYS; default: throw new IllegalArgumentException(timeUnit.toString()); } } /** * Parses a TimeValue in the format {@code }, for example {@code "1200 MILLISECONDS"}. *

* Parses: *

*
    *
  • {@code "1200 MILLISECONDS"}.
  • *
  • {@code " 1200 MILLISECONDS "}, spaces are ignored.
  • *
  • {@code "1 MINUTE"}, singular units.
  • *
  • *
* * * @param value the TimeValue to parse * @return a new TimeValue * @throws ParseException if the number cannot be parsed */ public static TimeValue parse(final String value) throws ParseException { final String split[] = value.trim().split("\\s+"); if (split.length < 2) { throw new IllegalArgumentException( String.format("Expected format for : %s", value)); } final String clean0 = split[0].trim(); final String clean1 = split[1].trim().toUpperCase(Locale.ROOT); final String timeUnitStr = clean1.endsWith("S") ? clean1 : clean1 + "S"; return TimeValue.of(Long.parseLong(clean0), TimeUnit.valueOf(timeUnitStr)); } private final long duration; private final TimeUnit timeUnit; TimeValue(final long duration, final TimeUnit timeUnit) { super(); this.duration = duration; this.timeUnit = Args.notNull(timeUnit, "timeUnit"); } public long convert(final TimeUnit targetTimeUnit) { Args.notNull(targetTimeUnit, "timeUnit"); return targetTimeUnit.convert(duration, timeUnit); } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj instanceof TimeValue) { final TimeValue that = (TimeValue) obj; final long thisDuration = this.convert(TimeUnit.NANOSECONDS); final long thatDuration = that.convert(TimeUnit.NANOSECONDS); return thisDuration == thatDuration; } return false; } /** * Returns a TimeValue whose value is {@code (this / divisor)}. * * @param divisor * value by which this TimeValue is to be divided. * @return {@code this / divisor} * @throws ArithmeticException * if {@code divisor} is zero. */ public TimeValue divide(final long divisor) { final long newDuration = duration / divisor; return of(newDuration, timeUnit); } /** * Returns a TimeValue whose value is {@code (this / divisor)}. * * @param divisor * value by which this TimeValue is to be divided. * @param targetTimeUnit * the target TimeUnit * @return {@code this / divisor} * @throws ArithmeticException * if {@code divisor} is zero. */ public TimeValue divide(final long divisor, final TimeUnit targetTimeUnit) { return of(convert(targetTimeUnit) / divisor, targetTimeUnit); } public long getDuration() { return duration; } public TimeUnit getTimeUnit() { return timeUnit; } @Override public int hashCode() { int hash = LangUtils.HASH_SEED; hash = LangUtils.hashCode(hash, this.convert(TimeUnit.NANOSECONDS)); return hash; } public TimeValue min(final TimeValue other) { return this.compareTo(other) > 0 ? other : this; } private TimeUnit min(final TimeUnit other) { return scale() > scale(other) ? other : getTimeUnit(); } private int scale() { return scale(timeUnit); } /** * Returns a made up scale for TimeUnits. * * @param tUnit * a TimeUnit * @return a number from 1 to 7, where 1 is NANOSECONDS and 7 DAYS. */ private int scale(final TimeUnit tUnit) { switch (tUnit) { case NANOSECONDS: return 1; case MICROSECONDS: return 2; case MILLISECONDS: return 3; case SECONDS: return 4; case MINUTES: return 5; case HOURS: return 6; case DAYS: return 7; default: // Should never happens unless Java adds to the enum. throw new IllegalStateException(); } } public void sleep() throws InterruptedException { timeUnit.sleep(duration); } public void timedJoin(final Thread thread) throws InterruptedException { timeUnit.timedJoin(thread, duration); } public void timedWait(final Object obj) throws InterruptedException { timeUnit.timedWait(obj, duration); } public long toDays() { return timeUnit.toDays(duration); } /** * Converts this instance of to a Duration. * * @return a Duration. * @since 5.2 */ public Duration toDuration() { return duration == 0 ? Duration.ZERO : Duration.of(duration, toChronoUnit(timeUnit)); } public long toHours() { return timeUnit.toHours(duration); } public long toMicroseconds() { return timeUnit.toMicros(duration); } public long toMilliseconds() { return timeUnit.toMillis(duration); } public int toMillisecondsIntBound() { return asBoundInt(toMilliseconds()); } public long toMinutes() { return timeUnit.toMinutes(duration); } public long toNanoseconds() { return timeUnit.toNanos(duration); } public long toSeconds() { return timeUnit.toSeconds(duration); } public int toSecondsIntBound() { return asBoundInt(toSeconds()); } @Override public int compareTo(final TimeValue other) { final TimeUnit targetTimeUnit = min(other.getTimeUnit()); return Long.compare(convert(targetTimeUnit), other.convert(targetTimeUnit)); } @Override public String toString() { return String.format("%d %s", duration, timeUnit); } public Timeout toTimeout() { return Timeout.of(duration, timeUnit); } } httpcore5/src/main/java/org/apache/hc/core5/util/Timeout.java0100644 0000000 0000000 00000015661 00000250600 023032 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import java.text.ParseException; import java.time.Duration; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; /** * Represents a timeout value as a non-negative {@code long} time and {@link TimeUnit}. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class Timeout extends TimeValue { /** * A zero milliseconds {@link Timeout}. */ public static final Timeout ZERO_MILLISECONDS = Timeout.of(0, TimeUnit.MILLISECONDS); /** * A one milliseconds {@link Timeout}. */ public static final Timeout ONE_MILLISECOND = Timeout.of(1, TimeUnit.MILLISECONDS); /** * A disabled timeout represented as 0 {@code MILLISECONDS}. */ public static final Timeout DISABLED = ZERO_MILLISECONDS; /** * An infinite timeout represented as 0 {@code MILLISECONDS}. * * @since 5.3 */ public static final Timeout INFINITE = ZERO_MILLISECONDS; /** * Returns the given {@code timeout} if it is not {@code null}, if {@code null} then returns {@link #DISABLED}. * * @param timeout may be {@code null} * @return {@code timeValue} or {@link #DISABLED} */ public static Timeout defaultsToDisabled(final Timeout timeout) { return defaultsTo(timeout, DISABLED); } /** * Returns the given {@code timeout} if it is not {@code null}, if {@code null} then returns {@link #INFINITE}. * * @param timeout may be {@code null} * @return {@code timeValue} or {@link #INFINITE} * * @since 5.3 */ public static Timeout defaultsToInfinite(final Timeout timeout) { return defaultsTo(timeout, DISABLED); } /** * Creates a Timeout from a Duration. * * @param duration the time duration. * @return a Timeout. * @since 5.2 */ public static Timeout of(final Duration duration) { final long seconds = duration.getSeconds(); final long nanoOfSecond = duration.getNano(); if (seconds == 0) { // no conversion return of(nanoOfSecond, TimeUnit.NANOSECONDS); } else if (nanoOfSecond == 0) { // no conversion return of(seconds, TimeUnit.SECONDS); } // conversion attempts try { return of(duration.toNanos(), TimeUnit.NANOSECONDS); } catch (final ArithmeticException e) { try { return of(duration.toMillis(), TimeUnit.MILLISECONDS); } catch (final ArithmeticException e1) { // backstop return of(seconds, TimeUnit.SECONDS); } } } /** * Creates a Timeout. * * @param duration the time duration. * @param timeUnit the time unit for the given duration. * @return a Timeout */ public static Timeout of(final long duration, final TimeUnit timeUnit) { return new Timeout(duration, timeUnit); } /** * Creates a Timeout. * * @param days the duration in days and the given {@code timeUnit}. * @return a Timeout */ public static Timeout ofDays(final long days) { return of(days, TimeUnit.DAYS); } /** * Creates a Timeout. * * @param hours the duration in hours and the given {@code timeUnit}. * @return a Timeout */ public static Timeout ofHours(final long hours) { return of(hours, TimeUnit.HOURS); } /** * Creates a Timeout. * * @param microseconds the duration in seconds and the given {@code timeUnit}. * @return a Timeout */ public static Timeout ofMicroseconds(final long microseconds) { return of(microseconds, TimeUnit.MICROSECONDS); } /** * Creates a Timeout. * * @param milliseconds the duration in milliseconds and the given {@code timeUnit}. * @return a Timeout */ public static Timeout ofMilliseconds(final long milliseconds) { return of(milliseconds, TimeUnit.MILLISECONDS); } /** * Creates a Timeout. * * @param minutes the duration in minutes and the given {@code timeUnit}. * @return a Timeout */ public static Timeout ofMinutes(final long minutes) { return of(minutes, TimeUnit.MINUTES); } /** * Creates a Timeout. * * @param nanoseconds the duration in seconds and the given {@code timeUnit}. * @return a Timeout */ public static Timeout ofNanoseconds(final long nanoseconds) { return of(nanoseconds, TimeUnit.NANOSECONDS); } /** * Creates a Timeout. * * @param seconds the duration in seconds and the given {@code timeUnit}. * @return a Timeout */ public static Timeout ofSeconds(final long seconds) { return of(seconds, TimeUnit.SECONDS); } /** * Parses a Timeout in the format {@code }, for example {@code "1,200 MILLISECONDS"} * * @param value the TimeValue to parse * @return a new TimeValue * @throws ParseException if the number cannot be parsed */ public static Timeout parse(final String value) throws ParseException { return TimeValue.parse(value).toTimeout(); } Timeout(final long duration, final TimeUnit timeUnit) { super(Args.notNegative(duration, "duration"), Args.notNull(timeUnit, "timeUnit")); } /** * Whether this timeout is disabled. * * @return Whether this timeout is disabled. */ public boolean isDisabled() { return getDuration() == 0; } /** * Whether this timeout is enabled. * * @return Whether this timeout is disabled. */ public boolean isEnabled() { return !isDisabled(); } } httpcore5/src/main/java/org/apache/hc/core5/util/TimeoutValueException.java0100644 0000000 0000000 00000006404 00000250600 025701 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import java.util.concurrent.TimeoutException; /** * A specialization of {@link TimeoutException} that carries a {@link Timeout} deadline and the actual value. * * @since 5.0 */ public class TimeoutValueException extends TimeoutException { private static final long serialVersionUID = 1L; /** * Creates a new exception for the given timeout deadline and actual timeout. * * @param timeoutDeadline How long was the expected timeout in milliseconds. * @param timeoutActual How long we actually waited in milliseconds. * @return a new TimeoutValueException. */ public static TimeoutValueException fromMilliseconds(final long timeoutDeadline, final long timeoutActual) { return new TimeoutValueException(Timeout.ofMilliseconds(min0(timeoutDeadline)), Timeout.ofMilliseconds(min0(timeoutActual))); } /** * Returns the given {@code value} if positive, otherwise returns 0. * * @param value any timeout * @return the given {@code value} if positive, otherwise returns 0. */ private static long min0(final long value) { return value < 0 ? 0 : value; } private final Timeout actual; private final Timeout deadline; /** * Creates a new exception for the given timeout deadline and actual timeout. * * @param deadline How long was the expected timeout. * @param actual How long we actually waited. */ public TimeoutValueException(final Timeout deadline, final Timeout actual) { super(String.format("Timeout deadline: %s, actual: %s", deadline, actual)); this.actual = actual; this.deadline = deadline; } /** * Gets how long was the expected timeout in milliseconds. * * @return how long was the expected timeout in milliseconds. */ public Timeout getActual() { return actual; } /** * Gets how long we actually waited in milliseconds. * * @return how long we actually waited in milliseconds. */ public Timeout getDeadline() { return deadline; } } httpcore5/src/main/java/org/apache/hc/core5/util/Tokenizer.java0100644 0000000 0000000 00000036721 00000250600 023356 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import java.util.BitSet; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; /** * Tokenizer that can be used as a foundation for more complex parsing routines. * Methods of this class are designed to produce near zero intermediate garbage * and make no intermediate copies of input data. *

* This class is immutable and thread safe. * * @since 5.1 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class Tokenizer { public static class Cursor { private final int lowerBound; private final int upperBound; private int pos; public Cursor(final int lowerBound, final int upperBound) { super(); Args.notNegative(lowerBound, "lowerBound"); Args.check(lowerBound <= upperBound, "lowerBound cannot be greater than upperBound"); this.lowerBound = lowerBound; this.upperBound = upperBound; this.pos = lowerBound; } public int getLowerBound() { return this.lowerBound; } public int getUpperBound() { return this.upperBound; } public int getPos() { return this.pos; } public void updatePos(final int pos) { Args.check(pos >= this.lowerBound, "pos: %s < lowerBound: %s", pos, this.lowerBound); Args.check(pos <= this.upperBound, "pos: %s > upperBound: %s", pos, this.upperBound); this.pos = pos; } public boolean atEnd() { return this.pos >= this.upperBound; } @Override public String toString() { final StringBuilder buffer = new StringBuilder(); buffer.append('['); buffer.append(this.lowerBound); buffer.append('>'); buffer.append(this.pos); buffer.append('>'); buffer.append(this.upperBound); buffer.append(']'); return buffer.toString(); } } /** * @deprecated Do not use. */ @Deprecated public static BitSet INIT_BITSET(final int ... b) { final BitSet bitset = new BitSet(); for (final int aB : b) { bitset.set(aB); } return bitset; } /** Double quote */ public static final char DQUOTE = '\"'; /** Backward slash / escape character */ public static final char ESCAPE = '\\'; public static final int CR = 13; // public static final int LF = 10; // public static final int SP = 32; // public static final int HT = 9; // public static boolean isWhitespace(final char ch) { return ch == SP || ch == HT || ch == CR || ch == LF; } /** * Represents a predicate whether the given character is a delimiter. * * @since 5.3 */ @FunctionalInterface public interface Delimiter { boolean test(char ch); } /** * @since 5.3 */ public static Delimiter delimiters(final BitSet delimiters) { return delimiters::get; } /** * @since 5.3 */ public static Delimiter delimiters(final char... delimiters) { return ch -> { for (final char delimiter : delimiters) { if (delimiter == ch) { return true; } } return false; }; } /** * @since 5.3 */ public static Delimiter delimiters(final char delimiter) { return ch -> ch == delimiter; } /** * @since 5.3 */ public static Delimiter delimiters(final char delimiter1, final char delimiter2) { return ch -> ch == delimiter1 || ch == delimiter2; } /** * @since 5.3 */ public static Delimiter delimiters(final char delimiter1, final char delimiter2, final char delimiter3) { return ch -> ch == delimiter1 || ch == delimiter2 || ch == delimiter3; } public static final Tokenizer INSTANCE = new Tokenizer(); /** * Extracts from the sequence of chars a token terminated with any of the given delimiters * or a whitespace characters. * * @param buf buffer with the sequence of chars to be parsed * @param cursor defines the bounds and current position of the buffer * @param delimiterPredicate delimiter predicate. Can be {@code null} if the token * is not delimited by any character. */ public String parseContent(final CharSequence buf, final Cursor cursor, final Delimiter delimiterPredicate) { Args.notNull(buf, "Char sequence"); Args.notNull(cursor, "Parser cursor"); final StringBuilder dst = new StringBuilder(); copyContent(buf, cursor, delimiterPredicate, dst); return dst.toString(); } /** * @deprecated use {@link #parseContent(CharSequence, Cursor, Delimiter)} */ @Deprecated public String parseContent(final CharSequence buf, final Cursor cursor, final BitSet bitSet) { return parseContent(buf, cursor, bitSet != null ? bitSet::get : null); } /** * Extracts from the sequence of chars a token terminated with any of the given delimiters * discarding semantically insignificant whitespace characters. * * @param buf buffer with the sequence of chars to be parsed * @param cursor defines the bounds and current position of the buffer * @param delimiterPredicate delimiter predicate. Can be {@code null} if the token * is not delimited by any character. */ public String parseToken(final CharSequence buf, final Cursor cursor, final Delimiter delimiterPredicate) { Args.notNull(buf, "Char sequence"); Args.notNull(cursor, "Parser cursor"); final StringBuilder dst = new StringBuilder(); boolean whitespace = false; while (!cursor.atEnd()) { final char current = buf.charAt(cursor.getPos()); if (delimiterPredicate != null && delimiterPredicate.test(current)) { break; } else if (isWhitespace(current)) { skipWhiteSpace(buf, cursor); whitespace = true; } else { if (whitespace && dst.length() > 0) { dst.append(' '); } copyContent(buf, cursor, delimiterPredicate, dst); whitespace = false; } } return dst.toString(); } /** * @deprecated use {@link #parseToken(CharSequence, Cursor, Delimiter)} */ @Deprecated public String parseToken(final CharSequence buf, final Cursor cursor, final BitSet bitSet) { return parseToken(buf, cursor, bitSet != null ? bitSet::get : null); } /** * Extracts from the sequence of chars a value which can be enclosed in quote marks and * terminated with any of the given delimiters discarding semantically insignificant * whitespace characters. * * @param buf buffer with the sequence of chars to be parsed * @param cursor defines the bounds and current position of the buffer * @param delimiterPredicate delimiter predicate. Can be {@code null} if the token * is not delimited by any character. */ public String parseValue(final CharSequence buf, final Cursor cursor, final Delimiter delimiterPredicate) { Args.notNull(buf, "Char sequence"); Args.notNull(cursor, "Parser cursor"); final StringBuilder dst = new StringBuilder(); boolean whitespace = false; while (!cursor.atEnd()) { final char current = buf.charAt(cursor.getPos()); if (delimiterPredicate != null && delimiterPredicate.test(current)) { break; } else if (isWhitespace(current)) { skipWhiteSpace(buf, cursor); whitespace = true; } else if (current == DQUOTE) { if (whitespace && dst.length() > 0) { dst.append(' '); } copyQuotedContent(buf, cursor, dst); whitespace = false; } else { if (whitespace && dst.length() > 0) { dst.append(' '); } copyUnquotedContent(buf, cursor, delimiterPredicate, dst); whitespace = false; } } return dst.toString(); } /** * @deprecated use {@link #parseValue(CharSequence, Cursor, Delimiter)} */ @Deprecated public String parseValue(final CharSequence buf, final Cursor cursor, final BitSet bitSet) { return parseValue(buf, cursor, bitSet != null ? bitSet::get : null); } /** * Skips semantically insignificant whitespace characters and moves the cursor to the closest * non-whitespace character. * * @param buf buffer with the sequence of chars to be parsed * @param cursor defines the bounds and current position of the buffer */ public void skipWhiteSpace(final CharSequence buf, final Cursor cursor) { Args.notNull(buf, "Char sequence"); Args.notNull(cursor, "Parser cursor"); int pos = cursor.getPos(); final int indexFrom = cursor.getPos(); final int indexTo = cursor.getUpperBound(); for (int i = indexFrom; i < indexTo; i++) { final char current = buf.charAt(i); if (!isWhitespace(current)) { break; } pos++; } cursor.updatePos(pos); } /** * Transfers content into the destination buffer until a whitespace character or any of * the given delimiters is encountered. * * @param buf buffer with the sequence of chars to be parsed * @param cursor defines the bounds and current position of the buffer * @param delimiterPredicate delimiter predicate. Can be {@code null} if the token * is delimited by a whitespace only. * @param dst destination buffer */ public void copyContent(final CharSequence buf, final Cursor cursor, final Delimiter delimiterPredicate, final StringBuilder dst) { Args.notNull(buf, "Char sequence"); Args.notNull(cursor, "Parser cursor"); Args.notNull(dst, "String builder"); int pos = cursor.getPos(); final int indexFrom = cursor.getPos(); final int indexTo = cursor.getUpperBound(); for (int i = indexFrom; i < indexTo; i++) { final char current = buf.charAt(i); if (delimiterPredicate != null && delimiterPredicate.test(current) || isWhitespace(current)) { break; } pos++; dst.append(current); } cursor.updatePos(pos); } /** * @deprecated Use {@link #copyContent(CharSequence, Cursor, Delimiter, StringBuilder)} */ @Deprecated public void copyContent(final CharSequence buf, final Cursor cursor, final BitSet bitSet, final StringBuilder dst) { copyContent(buf, cursor, bitSet != null ? bitSet::get : null, dst); } /** * Transfers content into the destination buffer until a whitespace character, a quote, * or any of the given delimiters is encountered. * * @param buf buffer with the sequence of chars to be parsed * @param cursor defines the bounds and current position of the buffer * @param delimiterPredicate delimiter predicate. Can be {@code null} if the token * is delimited by a whitespace or a quote only. * @param dst destination buffer */ public void copyUnquotedContent(final CharSequence buf, final Cursor cursor, final Delimiter delimiterPredicate, final StringBuilder dst) { Args.notNull(buf, "Char sequence"); Args.notNull(cursor, "Parser cursor"); Args.notNull(dst, "String builder"); int pos = cursor.getPos(); final int indexFrom = cursor.getPos(); final int indexTo = cursor.getUpperBound(); for (int i = indexFrom; i < indexTo; i++) { final char current = buf.charAt(i); if (delimiterPredicate != null && delimiterPredicate.test(current) || isWhitespace(current) || current == DQUOTE) { break; } pos++; dst.append(current); } cursor.updatePos(pos); } /** * @deprecated Use {@link #copyUnquotedContent(CharSequence, Cursor, Delimiter, StringBuilder)} */ @Deprecated public void copyUnquotedContent(final CharSequence buf, final Cursor cursor, final BitSet bitSet, final StringBuilder dst) { copyUnquotedContent(buf, cursor, bitSet != null ? bitSet::get : null, dst); } /** * Transfers content enclosed with quote marks into the destination buffer. * * @param buf buffer with the sequence of chars to be parsed * @param cursor defines the bounds and current position of the buffer * @param dst destination buffer */ public void copyQuotedContent(final CharSequence buf, final Cursor cursor, final StringBuilder dst) { Args.notNull(buf, "Char sequence"); Args.notNull(cursor, "Parser cursor"); Args.notNull(dst, "String builder"); if (cursor.atEnd()) { return; } int pos = cursor.getPos(); int indexFrom = cursor.getPos(); final int indexTo = cursor.getUpperBound(); char current = buf.charAt(pos); if (current != DQUOTE) { return; } pos++; indexFrom++; boolean escaped = false; for (int i = indexFrom; i < indexTo; i++, pos++) { current = buf.charAt(i); if (escaped) { if (current != DQUOTE && current != ESCAPE) { dst.append(ESCAPE); } dst.append(current); escaped = false; } else { if (current == DQUOTE) { pos++; break; } if (current == ESCAPE) { escaped = true; } else if (current != CR && current != LF) { dst.append(current); } } } cursor.updatePos(pos); } } httpcore5/src/main/java/org/apache/hc/core5/util/VersionInfo.java0100644 0000000 0000000 00000026260 00000250600 023642 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Properties; /** * Provides access to version information for HTTP components. * Static methods are used to extract version information from property * files that are automatically packaged with HTTP component release JARs. *

* All available version information is provided in strings, where * the string format is informal and subject to change without notice. * Version information is provided for debugging output and interpretation * by humans, not for automated processing in applications. *

* * @since 4.0 */ public class VersionInfo { /** A string constant for unavailable information. */ public final static String UNAVAILABLE = "UNAVAILABLE"; /** The filename of the version information files. */ public final static String VERSION_PROPERTY_FILE = "version.properties"; // the property names public final static String PROPERTY_MODULE = "info.module"; public final static String PROPERTY_RELEASE = "info.release"; /** * @deprecated This will be removed in 6.0. */ @Deprecated public final static String PROPERTY_TIMESTAMP = "info.timestamp"; /** The package that contains the version information. */ private final String infoPackage; /** The module from the version info. */ private final String infoModule; /** The release from the version info. */ private final String infoRelease; /** The timestamp from the version info. */ private final String infoTimestamp; /** The classloader from which the version info was obtained. */ private final String infoClassloader; /** * An empty immutable {@code VersionInfo} array. */ private static final VersionInfo[] EMPTY_VERSION_INFO_ARRAY = {}; /** * Instantiates version information. * * @param pckg the package * @param module the module, or {@code null} * @param release the release, or {@code null} * @param time the build time, or {@code null} * @param clsldr the class loader, or {@code null} */ protected VersionInfo(final String pckg, final String module, final String release, final String time, final String clsldr) { Args.notNull(pckg, "Package identifier"); infoPackage = pckg; infoModule = module != null ? module : UNAVAILABLE; infoRelease = release != null ? release : UNAVAILABLE; infoTimestamp = time != null ? time : UNAVAILABLE; infoClassloader = clsldr != null ? clsldr : UNAVAILABLE; } /** * Obtains the package name. * The package name identifies the module or informal unit. * * @return the package name, never {@code null} */ public final String getPackage() { return infoPackage; } /** * Obtains the name of the versioned module or informal unit. * This data is read from the version information for the package. * * @return the module name, never {@code null} */ public final String getModule() { return infoModule; } /** * Obtains the release of the versioned module or informal unit. * This data is read from the version information for the package. * * @return the release version, never {@code null} */ public final String getRelease() { return infoRelease; } /** * Obtains the timestamp of the versioned module or informal unit. * This data is read from the version information for the package. * * @return the timestamp, never {@code null} * @deprecated This will be removed in 6.0. */ @Deprecated public final String getTimestamp() { return infoTimestamp; } /** * Obtains the classloader used to read the version information. * This is just the {@code toString} output of the classloader, * since the version information should not keep a reference to * the classloader itself. That could prevent garbage collection. * * @return the classloader description, never {@code null} */ public final String getClassloader() { return infoClassloader; } /** * Provides the version information in human-readable format. * * @return a string holding this version information */ @Override public String toString() { final StringBuilder sb = new StringBuilder (20 + infoPackage.length() + infoModule.length() + infoRelease.length() + infoTimestamp.length() + infoClassloader.length()); sb.append("VersionInfo(") .append(infoPackage).append(':').append(infoModule); // If version info is missing, a single "UNAVAILABLE" for the module // is sufficient. Everything else just clutters the output. if (!UNAVAILABLE.equals(infoRelease)) { sb.append(':').append(infoRelease); } sb.append(')'); if (!UNAVAILABLE.equals(infoClassloader)) { sb.append('@').append(infoClassloader); } return sb.toString(); } /** * Loads version information for a list of packages. * * @param pckgs the packages for which to load version info * @param clsldr the classloader to load from, or * {@code null} for the thread context classloader * * @return the version information for all packages found, * never {@code null} */ public static VersionInfo[] loadVersionInfo(final String[] pckgs, final ClassLoader clsldr) { Args.notNull(pckgs, "Package identifier array"); final List vil = new ArrayList<>(pckgs.length); for (final String pckg : pckgs) { final VersionInfo vi = loadVersionInfo(pckg, clsldr); if (vi != null) { vil.add(vi); } } return vil.toArray(EMPTY_VERSION_INFO_ARRAY); } /** * Loads version information for a package. * * @param pckg the package for which to load version information, * for example "org.apache.http". * The package name should NOT end with a dot. * @param clsldr the classloader to load from, or * {@code null} for the thread context classloader * * @return the version information for the argument package, or * {@code null} if not available */ public static VersionInfo loadVersionInfo(final String pckg, final ClassLoader clsldr) { Args.notNull(pckg, "Package identifier"); final ClassLoader cl = clsldr != null ? clsldr : Thread.currentThread().getContextClassLoader(); Properties vip = null; // version info properties, if available try { // org.apache.http becomes // org/apache/http/version.properties try (final InputStream is = cl.getResourceAsStream(pckg.replace('.', '/') + "/" + VERSION_PROPERTY_FILE)) { if (is != null) { final Properties props = new Properties(); props.load(is); vip = props; } } } catch (final IOException ex) { // shamelessly munch this exception } VersionInfo result = null; if (vip != null) { result = fromMap(pckg, vip, cl); } return result; } /** * Instantiates version information from properties. * * @param pckg the package for the version information * @param info the map from string keys to string values, * for example {@link Properties} * @param clsldr the classloader, or {@code null} * * @return the version information */ protected static VersionInfo fromMap(final String pckg, final Map info, final ClassLoader clsldr) { Args.notNull(pckg, "Package identifier"); String module = null; String release = null; if (info != null) { module = (String) info.get(PROPERTY_MODULE); if (module != null && module.length() < 1) { module = null; } release = (String) info.get(PROPERTY_RELEASE); if (release != null && (release.length() < 1 || release.equals("${project.version}"))) { release = null; } } // if info String clsldrstr = null; if (clsldr != null) { clsldrstr = clsldr.toString(); } return new VersionInfo(pckg, module, release, null, clsldrstr); } /** * Gets software information as {@code "/ (Java/)"}. If release is * {@link #UNAVAILABLE}, it will be omitted. *

* For example: *

"Apache-HttpClient/4.3 (Java/1.6.0_35)"
* * @param name the component name, like "Apache-HttpClient". * @param pkg * the package for which to load version information, for example "org.apache.http". The package name * should NOT end with a dot. * @param cls * the class' class loader to load from, or {@code null} for the thread context class loader * @since 4.3 */ public static String getSoftwareInfo(final String name, final String pkg, final Class cls) { // determine the release version from packaged version info final VersionInfo vi = VersionInfo.loadVersionInfo(pkg, cls.getClassLoader()); final String release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE; final String javaVersion = System.getProperty("java.version"); String nameAndRelease = name; if (!UNAVAILABLE.equals(release)) { nameAndRelease += "/" + release; } return String.format("%s (Java/%s)", nameAndRelease, javaVersion); } } httpcore5/src/main/java/org/apache/hc/core5/util/package-info.java0100644 0000000 0000000 00000002344 00000250600 023722 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Core utility classes. */ package org.apache.hc.core5.util; httpcore5/src/main/resources/0040755 0000000 0000000 00000000000 00000250600 015230 5ustar000000000 0000000 httpcore5/src/main/resources/org/0040755 0000000 0000000 00000000000 00000250600 016017 5ustar000000000 0000000 httpcore5/src/main/resources/org/apache/0040755 0000000 0000000 00000000000 00000250600 017240 5ustar000000000 0000000 httpcore5/src/main/resources/org/apache/hc/0040755 0000000 0000000 00000000000 00000250600 017632 5ustar000000000 0000000 httpcore5/src/main/resources/org/apache/hc/core5/0040755 0000000 0000000 00000000000 00000250600 020647 5ustar000000000 0000000 httpcore5/src/main/resources/org/apache/hc/core5/version.properties0100644 0000000 0000000 00000001540 00000250600 024447 0ustar000000000 0000000 # # 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. # info.module = ${project.artifactId} info.release = ${project.version} httpcore5/src/test/0040755 0000000 0000000 00000000000 00000250600 013251 5ustar000000000 0000000 httpcore5/src/test/java/0040755 0000000 0000000 00000000000 00000250600 014172 5ustar000000000 0000000 httpcore5/src/test/java/org/0040755 0000000 0000000 00000000000 00000250600 014761 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/0040755 0000000 0000000 00000000000 00000250600 016202 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/0040755 0000000 0000000 00000000000 00000250600 016574 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/0040755 0000000 0000000 00000000000 00000250600 017611 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/annotation/0040755 0000000 0000000 00000000000 00000250600 021763 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/annotation/ThreadingBehaviorTest.java0100644 0000000 0000000 00000003463 00000250600 027056 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.annotation; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; class ThreadingBehaviorTest { @Test void testName() { assertEquals("SAFE", ThreadingBehavior.SAFE.name()); assertEquals("SAFE_CONDITIONAL", ThreadingBehavior.SAFE_CONDITIONAL.name()); assertEquals("IMMUTABLE_CONDITIONAL", ThreadingBehavior.IMMUTABLE_CONDITIONAL.name()); assertEquals("IMMUTABLE", ThreadingBehavior.IMMUTABLE.name()); assertEquals("STATELESS", ThreadingBehavior.STATELESS.name()); assertEquals("UNSAFE", ThreadingBehavior.UNSAFE.name()); } }httpcore5/src/test/java/org/apache/hc/core5/concurrent/0040755 0000000 0000000 00000000000 00000250600 021773 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/concurrent/CountDownLatchFutureCallback.java0100644 0000000 0000000 00000006261 00000250600 030344 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.concurrent; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Counts down all method calls for a {@link CountDownLatch}. * * @param the future result type consumed by this callback. */ public class CountDownLatchFutureCallback implements FutureCallback { private final CountDownLatch countDownLatch; /** * Constructs a new instance. * * @param count the number of times {@link CountDownLatch#countDown()} must be invoked before threads can pass through {@link CountDownLatch#await()} * @throws IllegalArgumentException if {@code count} is negative. */ public CountDownLatchFutureCallback(final int count) { this.countDownLatch = new CountDownLatch(count); } /** * Delegates to {@link CountDownLatch#await()}. * * @throws InterruptedException if the current thread is interrupted while waiting */ public void await() throws InterruptedException { countDownLatch.await(); } /** * Delegates to {@link CountDownLatch#await(long, TimeUnit)}. * * @param timeout the maximum time to wait. * @param unit the time unit of the {@code timeout} argument. * @return {@code true} if the count reached zero and {@code false} if the waiting time elapsed before the count reached zero. * @throws InterruptedException if the current thread is interrupted while waiting. */ public boolean await(final long timeout, final TimeUnit unit) throws InterruptedException { return countDownLatch.await(timeout, unit); } @Override public void cancelled() { countDownLatch.countDown(); } @Override public void completed(final T result) { countDownLatch.countDown(); } /** * Delegates to {@link CountDownLatch#countDown()}. */ public void countDown() { countDownLatch.countDown(); } @Override public void failed(final Exception ex) { countDownLatch.countDown(); } } httpcore5/src/test/java/org/apache/hc/core5/concurrent/CountingFutureCallback.java0100644 0000000 0000000 00000004127 00000250600 027235 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.concurrent; import java.util.concurrent.atomic.AtomicLong; /** * Counts all method calls. * * @param the future result type consumed by this callback. */ public class CountingFutureCallback implements FutureCallback { private final AtomicLong count = new AtomicLong(); @Override public void cancelled() { count.incrementAndGet(); } @Override public void completed(final T result) { count.incrementAndGet(); } @Override public void failed(final Exception ex) { count.incrementAndGet(); } /** * The atomic count as an AtomicLong. * * @return atomic count as an AtomicLong. */ public AtomicLong getAtomicCount() { return count; } /** * The atomic count as a long. * * @return atomic count as a long. */ public long getCount() { return count.longValue(); } } httpcore5/src/test/java/org/apache/hc/core5/concurrent/DefaultThreadFactoryTest.java0100644 0000000 0000000 00000003563 00000250600 027546 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.concurrent; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class DefaultThreadFactoryTest { @Test void newThread() throws Exception { final ThreadFactory defaultThreadFactory = new DefaultThreadFactory("I/O server dispatch", true); final CountDownLatch lockHeld = new CountDownLatch(1); final Thread thread = defaultThreadFactory.newThread(lockHeld::countDown); Assertions.assertNotNull(thread); thread.start(); Assertions.assertTrue(lockHeld.await(15, TimeUnit.SECONDS)); } }httpcore5/src/test/java/org/apache/hc/core5/concurrent/FutureCallbackAdapter.java0100644 0000000 0000000 00000004170 00000250600 027025 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.concurrent; /** * Provides an extensible default adatper for {@link FutureCallback} implementation. * * @param the future result type consumed by this callback. */ public class FutureCallbackAdapter implements FutureCallback { /** * The singleton instance. */ private static final FutureCallbackAdapter INSTANCE = new FutureCallbackAdapter<>(); /** * Get the singleton instance typed as {@code T}. * * @param the future result type consumed by this callback. * @return The singleton instance. */ @SuppressWarnings("unchecked") public static FutureCallbackAdapter getInstance() { return (FutureCallbackAdapter) INSTANCE; } @Override public void cancelled() { // noop } @Override public void completed(final T result) { // noop } @Override public void failed(final Exception ex) { // noop } } httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestBasicFuture.java0100644 0000000 0000000 00000024615 00000250600 025717 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.concurrent; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import static org.junit.jupiter.api.Assertions.fail; import java.time.Duration; import java.util.concurrent.CancellationException; 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; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.hc.core5.util.TimeoutValueException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; class TestBasicFuture { @Test void testCompleted() throws Exception { final FutureCallback callback = Mockito.mock(FutureCallback.class); final BasicFuture future = new BasicFuture<>(callback); Assertions.assertFalse(future.isDone()); final Object result = new Object(); final Exception boom = new Exception(); future.completed(result); future.failed(boom); Mockito.verify(callback).completed(result); Mockito.verify(callback, Mockito.never()).failed(ArgumentMatchers.any()); Mockito.verify(callback, Mockito.never()).cancelled(); Assertions.assertSame(result, future.get()); Assertions.assertTrue(future.isDone()); Assertions.assertFalse(future.isCancelled()); } @Test void testCompletedWithTimeout() throws Exception { final FutureCallback callback = Mockito.mock(FutureCallback.class); final BasicFuture future = new BasicFuture<>(callback); Assertions.assertFalse(future.isDone()); final Object result = new Object(); final Exception boom = new Exception(); future.completed(result); future.failed(boom); Mockito.verify(callback).completed(result); Mockito.verify(callback, Mockito.never()).failed(ArgumentMatchers.any()); Mockito.verify(callback, Mockito.never()).cancelled(); Assertions.assertSame(result, future.get(1, TimeUnit.MILLISECONDS)); Assertions.assertTrue(future.isDone()); Assertions.assertFalse(future.isCancelled()); } @Test void testFailed() throws Exception { final FutureCallback callback = Mockito.mock(FutureCallback.class); final BasicFuture future = new BasicFuture<>(callback); final Object result = new Object(); final Exception boom = new Exception(); future.failed(boom); future.completed(result); Mockito.verify(callback, Mockito.never()).completed(ArgumentMatchers.any()); Mockito.verify(callback).failed(boom); Mockito.verify(callback, Mockito.never()).cancelled(); try { future.get(); } catch (final ExecutionException ex) { Assertions.assertSame(boom, ex.getCause()); } Assertions.assertTrue(future.isDone()); Assertions.assertFalse(future.isCancelled()); } @Test void testCancelled() { final FutureCallback callback = Mockito.mock(FutureCallback.class); final BasicFuture future = new BasicFuture<>(callback); final Object result = new Object(); final Exception boom = new Exception(); future.cancel(true); future.failed(boom); future.completed(result); Mockito.verify(callback, Mockito.never()).completed(ArgumentMatchers.any()); Mockito.verify(callback, Mockito.never()).failed(ArgumentMatchers.any()); Mockito.verify(callback).cancelled(); assertThrows(CancellationException.class, future::get); Assertions.assertTrue(future.isDone()); Assertions.assertTrue(future.isCancelled()); } @Test void testAsyncCompleted() throws Exception { final BasicFuture future = new BasicFuture<>(null); final Object result = new Object(); final Thread t = new Thread(() -> { try { Thread.sleep(100); future.completed(result); } catch (final InterruptedException expected) { } }); t.setDaemon(true); t.start(); Assertions.assertSame(result, future.get(60, TimeUnit.SECONDS)); Assertions.assertTrue(future.isDone()); Assertions.assertFalse(future.isCancelled()); } @Test void testAsyncFailed() throws Exception { final BasicFuture future = new BasicFuture<>(null); final Exception boom = new Exception(); final Thread t = new Thread(() -> { try { Thread.sleep(100); future.failed(boom); } catch (final InterruptedException expected) { } }); t.setDaemon(true); t.start(); try { future.get(60, TimeUnit.SECONDS); } catch (final ExecutionException ex) { Assertions.assertSame(boom, ex.getCause()); } Assertions.assertTrue(future.isDone()); Assertions.assertFalse(future.isCancelled()); } @Test void testAsyncCancelled() { final BasicFuture future = new BasicFuture<>(null); final Thread t = new Thread(() -> { try { Thread.sleep(100); future.cancel(true); } catch (final InterruptedException expected) { } }); t.setDaemon(true); t.start(); assertThrows(CancellationException.class, () -> future.get(60, TimeUnit.SECONDS)); } @Test void testAsyncTimeout() { final BasicFuture future = new BasicFuture<>(null); final Object result = new Object(); final Thread t = new Thread(() -> { try { Thread.sleep(200); future.completed(result); } catch (final InterruptedException expected) { } }); t.setDaemon(true); t.start(); assertThrows(TimeoutException.class, () -> future.get(1, TimeUnit.MILLISECONDS)); } @Test void testAsyncNegativeTimeout() { final BasicFuture future = new BasicFuture<>(null); assertThrows(TimeoutValueException.class, () -> future.get(-1, TimeUnit.MILLISECONDS)); } @Test void testConcurrentOperations() throws InterruptedException, ExecutionException { final FutureCallback callback = FutureCallbackAdapter.getInstance(); final ExecutorService executor = Executors.newFixedThreadPool(3); final BasicFuture future = new BasicFuture<>(callback); final Object expectedResult = new Object(); final AtomicBoolean completedSuccessfully = new AtomicBoolean(); final AtomicBoolean failedSuccessfully = new AtomicBoolean(); final AtomicBoolean cancelledSuccessfully = new AtomicBoolean(); // Run 3 tasks concurrently: complete, fail, and cancel the future. final Future future1 = executor.submit(() -> completedSuccessfully.set(future.completed(expectedResult))); final Future future2 = executor.submit(() -> failedSuccessfully.set(future.failed(new Exception("Test Exception")))); final Future future3 = executor.submit(() -> cancelledSuccessfully.set(future.cancel())); // Wait for the tasks to finish. future1.get(); future2.get(); future3.get(); // Verify that the first operation won and the other two failed. if (completedSuccessfully.get()) { assertEquals(expectedResult, future.get()); } else if (failedSuccessfully.get()) { assertThrows(ExecutionException.class, future::get); } else if (cancelledSuccessfully.get()) { assertThrows(CancellationException.class, future::get); } else { fail("No operation was successful on the future."); } // Shutdown the executor. executor.shutdown(); } @Test void testGetWithTimeout() { final AtomicBoolean isFutureCompleted = new AtomicBoolean(); final FutureCallback callback = new FutureCallbackAdapter() { @Override public void completed(final String result) { isFutureCompleted.set(true); } }; final BasicFuture future = new BasicFuture<>(callback); new Thread(() -> future.completed("test")).start(); // Poll until the future is completed or timeout assertTimeoutPreemptively(Duration.ofMillis(200), () -> { while (!isFutureCompleted.get()) { // This loop will spin until the future is completed or the assertTimeoutPreemptively times out. Thread.yield(); } try { assertEquals("test", future.get(1, TimeUnit.SECONDS)); } catch (final ExecutionException | TimeoutException e) { fail("Test failed due to exception: " + e.getMessage()); } }); } } httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestCompletedFuture.java0100644 0000000 0000000 00000003253 00000250600 026605 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.concurrent; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestCompletedFuture { @Test void testCompleted() { final Object result = new Object(); final CompletedFuture future = new CompletedFuture<>(result); Assertions.assertSame(result, future.get()); Assertions.assertTrue(future.isDone()); Assertions.assertFalse(future.isCancelled()); future.cancel(true); future.cancel(); } }httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestComplexCancellable.java0100644 0000000 0000000 00000005600 00000250600 027211 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.concurrent; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; import java.util.concurrent.CountDownLatch; class TestComplexCancellable { @Test void testCancelled() { final ComplexCancellable cancellable = new ComplexCancellable(); final BasicFuture dependency1 = new BasicFuture<>(null); cancellable.setDependency(dependency1); assertFalse(cancellable.isCancelled()); cancellable.cancel(); assertThat(cancellable.isCancelled(), CoreMatchers.is(true)); assertThat(dependency1.isCancelled(), CoreMatchers.is(true)); final BasicFuture dependency2 = new BasicFuture<>(null); cancellable.setDependency(dependency2); assertThat(dependency2.isCancelled(), CoreMatchers.is(true)); } @Test void testSetDependencyRace() throws InterruptedException { final ComplexCancellable cancellable = new ComplexCancellable(); final BasicFuture dependency1 = new BasicFuture<>(null); final BasicFuture dependency2 = new BasicFuture<>(null); final CountDownLatch latch = new CountDownLatch(2); for (int i = 0; i < 2; i++) { new Thread(() -> { for (int j = 0; j < 5_000; j++) { cancellable.setDependency(dependency1); cancellable.setDependency(dependency2); } latch.countDown(); }).start(); } latch.await(); assertFalse(dependency1.isCancelled()); assertFalse(dependency2.isCancelled()); } } httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestComplexFuture.java0100644 0000000 0000000 00000007245 00000250600 026305 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.concurrent; import static org.hamcrest.MatcherAssert.assertThat; import java.util.concurrent.Future; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestComplexFuture { @Test void testCancelled() { final ComplexFuture future = new ComplexFuture<>(null); final Future dependency1 = new BasicFuture<>(null); future.setDependency(dependency1); Assertions.assertFalse(future.isDone()); future.cancel(); assertThat(future.isCancelled(), CoreMatchers.is(true)); assertThat(dependency1.isCancelled(), CoreMatchers.is(true)); final Future dependency2 = new BasicFuture<>(null); future.setDependency(dependency2); assertThat(dependency2.isCancelled(), CoreMatchers.is(true)); } @Test void testCompleted() { final ComplexFuture future = new ComplexFuture<>(null); final Future dependency1 = new BasicFuture<>(null); future.setDependency(dependency1); Assertions.assertFalse(future.isDone()); future.completed(Boolean.TRUE); assertThat(future.isCancelled(), CoreMatchers.is(false)); assertThat(dependency1.isCancelled(), CoreMatchers.is(false)); final Future dependency2 = new BasicFuture<>(null); future.setDependency(dependency2); assertThat(dependency2.isCancelled(), CoreMatchers.is(true)); } @Test void testCancelledWithCallback() { final ComplexFuture future = new ComplexFuture<>(null); final Future dependency1 = new BasicFuture<>(new FutureContribution(future) { @Override public void completed(final Object result) { } }); future.setDependency(dependency1); Assertions.assertFalse(future.isDone()); future.cancel(); assertThat(future.isCancelled(), CoreMatchers.is(true)); assertThat(dependency1.isCancelled(), CoreMatchers.is(true)); final Future dependency2 = new BasicFuture<>(null); future.setDependency(dependency2); assertThat(dependency2.isCancelled(), CoreMatchers.is(true)); } @Test void testCanceledAndFailed() { final ComplexFuture future = new ComplexFuture<>(null); assertThat(future.cancel(), CoreMatchers.is(true)); assertThat(future.failed(new Exception()), CoreMatchers.is(false)); } } httpcore5/src/test/java/org/apache/hc/core5/http/0040755 0000000 0000000 00000000000 00000250600 020570 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/HeaderMatcher.java0100644 0000000 0000000 00000004450 00000250600 024127 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.util.Objects; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; public class HeaderMatcher extends BaseMatcher
{ private final String headerName; private final Object headerValue; public HeaderMatcher(final String headerName, final Object headerValue) { this.headerName = headerName; this.headerValue = headerValue; } @Override public boolean matches(final Object item) { if (item instanceof Header) { final Header header = (Header) item; if (headerName.equalsIgnoreCase(header.getName()) && Objects.equals(headerValue, header.getValue())) { return true; } } return false; } @Override public void describeTo(final Description description) { description.appendText("same header as ").appendValue(headerValue).appendText(": ").appendValue(headerValue); } public static Matcher
same(final String headerName, final Object headerValue) { return new HeaderMatcher(headerName, headerValue); } } httpcore5/src/test/java/org/apache/hc/core5/http/HeadersMatcher.java0100644 0000000 0000000 00000004757 00000250600 024324 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.util.Objects; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; public class HeadersMatcher extends BaseMatcher { private final Header[] expectedHeaders; public HeadersMatcher(final Header... headers) { this.expectedHeaders = headers; } @Override public boolean matches(final Object item) { if (item instanceof Header[]) { final Header[] headers = (Header[]) item; if (headers.length == expectedHeaders.length) { for (int i = 0; i < headers.length; i++) { final Header h1 = headers[i]; final Header h2 = expectedHeaders[i]; if (!h1.getName().equalsIgnoreCase(h2.getName()) || !Objects.equals(h1.getValue(), h2.getValue())) { return false; } } return true; } } return false; } @Override public void describeTo(final Description description) { description.appendText("same headers as ").appendValueList("[", "; ", "]", expectedHeaders); } public static Matcher same(final Header... headers) { return new HeadersMatcher(headers); } } httpcore5/src/test/java/org/apache/hc/core5/http/NameValuePairListMatcher.java0100644 0000000 0000000 00000005646 00000250600 026274 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; public class NameValuePairListMatcher extends BaseMatcher> { private final List nvps; public NameValuePairListMatcher(final List nvps) { this.nvps = nvps; } @Override public boolean matches(final Object item) { if (item instanceof List) { final List objects = (List) item; if (objects.size() != nvps.size()) { return false; } for (int i = 1; i < objects.size(); i++) { final Object obj = objects.get(i); if (obj instanceof NameValuePair) { final NameValuePair nvp = (NameValuePair) obj; final NameValuePair expected = nvps.get(i); if (!Objects.equals(nvp.getName(), expected.getName()) || !Objects.equals(nvp.getValue(), expected.getValue())) { return false; } } } return true; } return false; } @Override public void describeTo(final Description description) { description.appendText("equals ").appendValueList("[", ";", "]", nvps); } public static Matcher> equalsTo(final NameValuePair... nvps) { return new NameValuePairListMatcher(Arrays.asList(nvps)); } public static Matcher> isEmpty() { return new NameValuePairListMatcher(Collections.emptyList()); } } httpcore5/src/test/java/org/apache/hc/core5/http/ReadableByteChannelMock.java0100644 0000000 0000000 00000006012 00000250600 026055 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.ReadableByteChannel; import java.nio.charset.Charset; public class ReadableByteChannelMock implements ReadableByteChannel { private final byte[][] chunks; private int chunkCount = 0; private ByteBuffer currentChunk; private boolean eof = false; private boolean closed = false; public ReadableByteChannelMock(final String[] strings, final Charset charset) { super(); this.chunks = new byte[strings.length][]; for (int i = 0; i < strings.length; i++) { this.chunks[i] = strings[i].getBytes(charset); } } public ReadableByteChannelMock(final byte[]... chunks) { super(); this.chunks = chunks; } private void prepareChunk() { if (this.currentChunk == null || !this.currentChunk.hasRemaining()) { if (this.chunkCount < this.chunks.length) { final byte[] bytes = this.chunks[this.chunkCount]; this.chunkCount++; this.currentChunk = ByteBuffer.wrap(bytes); } else { this.eof = true; } } } @Override public int read(final ByteBuffer dst) throws IOException { if (this.closed) { throw new ClosedChannelException(); } prepareChunk(); if (this.eof) { return -1; } int i = 0; while (dst.hasRemaining() && this.currentChunk.hasRemaining()) { dst.put(this.currentChunk.get()); i++; } return i; } @Override public void close() throws IOException { this.closed = true; } @Override public boolean isOpen() { return !this.closed && !this.eof; } } httpcore5/src/test/java/org/apache/hc/core5/http/TestContentType.java0100644 0000000 0000000 00000024452 00000250600 024553 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.nio.charset.StandardCharsets; import java.util.Arrays; import org.apache.hc.core5.http.message.BasicNameValuePair; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link ContentType}. * */ class TestContentType { @Test void testApplicationZipCompressed() { final ContentType contentType = ContentType.create("application/x-zip-compressed"); Assertions.assertEquals(contentType.toString(), ContentType.APPLICATION_ZIP_COMPRESSED.toString()); Assertions.assertTrue(contentType.isSameMimeType(ContentType.APPLICATION_ZIP_COMPRESSED)); Assertions.assertTrue(ContentType.APPLICATION_ZIP_COMPRESSED.isSameMimeType(contentType)); } @Test void testBasis() throws Exception { final ContentType contentType = ContentType.create("text/plain", "US-ASCII"); Assertions.assertEquals("text/plain", contentType.getMimeType()); Assertions.assertEquals(StandardCharsets.US_ASCII, contentType.getCharset()); Assertions.assertEquals("text/plain; charset=US-ASCII", contentType.toString()); } @Test void testWithCharset() throws Exception { ContentType contentType = ContentType.create("text/plain", "US-ASCII"); Assertions.assertEquals("text/plain", contentType.getMimeType()); Assertions.assertEquals(StandardCharsets.US_ASCII, contentType.getCharset()); Assertions.assertEquals("text/plain; charset=US-ASCII", contentType.toString()); contentType = contentType.withCharset(StandardCharsets.UTF_8); Assertions.assertEquals("text/plain", contentType.getMimeType()); Assertions.assertEquals("UTF-8", contentType.getCharset().name()); Assertions.assertEquals("text/plain; charset=UTF-8", contentType.toString()); } @Test void testWithCharsetString() throws Exception { ContentType contentType = ContentType.create("text/plain", "US-ASCII"); Assertions.assertEquals("text/plain", contentType.getMimeType()); Assertions.assertEquals(StandardCharsets.US_ASCII, contentType.getCharset()); Assertions.assertEquals("text/plain; charset=US-ASCII", contentType.toString()); contentType = contentType.withCharset(StandardCharsets.UTF_8); Assertions.assertEquals("text/plain", contentType.getMimeType()); Assertions.assertEquals(StandardCharsets.UTF_8, contentType.getCharset()); Assertions.assertEquals("text/plain; charset=UTF-8", contentType.toString()); } @Test void testLowCaseText() throws Exception { final ContentType contentType = ContentType.create("Text/Plain", "ascii"); Assertions.assertEquals("text/plain", contentType.getMimeType()); Assertions.assertEquals(StandardCharsets.US_ASCII, contentType.getCharset()); } @Test void testCreateInvalidInput() { Assertions.assertThrows(NullPointerException.class, () -> ContentType.create(null, (String) null)); Assertions.assertThrows(IllegalArgumentException.class, () -> ContentType.create(" ", (String) null)); Assertions.assertThrows(IllegalArgumentException.class, () -> ContentType.create("stuff;", (String) null)); Assertions.assertThrows(IllegalArgumentException.class, () -> ContentType.create("text/plain", ",")); } @Test void testParse() throws Exception { final ContentType contentType = ContentType.parse("text/plain; charset=\"ascii\""); Assertions.assertEquals("text/plain", contentType.getMimeType()); Assertions.assertEquals(StandardCharsets.US_ASCII, contentType.getCharset()); Assertions.assertEquals("text/plain; charset=ascii", contentType.toString()); } @Test void testParseMultiparam() throws Exception { final ContentType contentType = ContentType.parse("text/plain; charset=\"ascii\"; " + "p0 ; p1 = \"blah-blah\" ; p2 = \" yada yada \" "); Assertions.assertEquals("text/plain", contentType.getMimeType()); Assertions.assertEquals(StandardCharsets.US_ASCII, contentType.getCharset()); Assertions.assertEquals("text/plain; charset=ascii; p0; p1=blah-blah; p2=\" yada yada \"", contentType.toString()); Assertions.assertNull(contentType.getParameter("p0")); Assertions.assertEquals("blah-blah", contentType.getParameter("p1")); Assertions.assertEquals(" yada yada ", contentType.getParameter("p2")); Assertions.assertNull(contentType.getParameter("p3")); } @Test void testParseEmptyCharset() throws Exception { final ContentType contentType = ContentType.parse("text/plain; charset=\" \""); Assertions.assertEquals("text/plain", contentType.getMimeType()); Assertions.assertNull(contentType.getCharset()); } @Test void testParseDefaultCharset() throws Exception { final ContentType contentType = ContentType.parse("text/plain; charset=\" \""); Assertions.assertEquals("text/plain", contentType.getMimeType()); Assertions.assertNull(contentType.getCharset()); Assertions.assertEquals(StandardCharsets.US_ASCII, contentType.getCharset(StandardCharsets.US_ASCII)); Assertions.assertNull(contentType.getCharset(null)); // Assertions.assertNull(ContentType.getCharset(contentType, null)); Assertions.assertEquals(StandardCharsets.US_ASCII, ContentType.getCharset(contentType, StandardCharsets.US_ASCII)); } @Test void testParseEmptyValue() throws Exception { Assertions.assertNull(ContentType.parse(null)); Assertions.assertNull(ContentType.parse("")); Assertions.assertNull(ContentType.parse(" ")); Assertions.assertNull(ContentType.parse(";")); Assertions.assertNull(ContentType.parse("=")); } @Test void testWithParamArrayChange() throws Exception { final BasicNameValuePair[] params = {new BasicNameValuePair("charset", "UTF-8"), new BasicNameValuePair("p", "this"), new BasicNameValuePair("p", "that")}; final ContentType contentType = ContentType.create("text/plain", params); Assertions.assertEquals("text/plain", contentType.getMimeType()); Assertions.assertEquals(StandardCharsets.UTF_8, contentType.getCharset()); Assertions.assertEquals("text/plain; charset=UTF-8; p=this; p=that", contentType.toString()); Arrays.setAll(params, i -> null); Assertions.assertEquals("this", contentType.getParameter("p")); Assertions.assertEquals("text/plain", contentType.getMimeType()); Assertions.assertEquals(StandardCharsets.UTF_8, contentType.getCharset()); Assertions.assertEquals("text/plain; charset=UTF-8; p=this; p=that", contentType.toString()); } @Test void testWithParams() throws Exception { ContentType contentType = ContentType.create("text/plain", new BasicNameValuePair("charset", "UTF-8"), new BasicNameValuePair("p", "this"), new BasicNameValuePair("p", "that")); Assertions.assertEquals("text/plain", contentType.getMimeType()); Assertions.assertEquals(StandardCharsets.UTF_8, contentType.getCharset()); Assertions.assertEquals("text/plain; charset=UTF-8; p=this; p=that", contentType.toString()); contentType = contentType.withParameters( new BasicNameValuePair("charset", "ascii"), new BasicNameValuePair("p", "this and that")); Assertions.assertEquals("text/plain", contentType.getMimeType()); Assertions.assertEquals(StandardCharsets.US_ASCII, contentType.getCharset()); Assertions.assertEquals("text/plain; charset=ascii; p=\"this and that\"", contentType.toString()); contentType = ContentType.create("text/blah").withParameters( new BasicNameValuePair("p", "blah")); Assertions.assertEquals("text/blah", contentType.getMimeType()); Assertions.assertNull(contentType.getCharset()); Assertions.assertEquals("text/blah; p=blah", contentType.toString()); contentType = ContentType.create("text/blah", StandardCharsets.ISO_8859_1).withParameters( new BasicNameValuePair("p", "blah")); Assertions.assertEquals("text/blah", contentType.getMimeType()); Assertions.assertEquals(StandardCharsets.ISO_8859_1, contentType.getCharset()); Assertions.assertEquals("text/blah; charset=ISO-8859-1; p=blah", contentType.toString()); } @Test void testParseLenientIgnoresIllegalCharsetName() { final ContentType contentType = ContentType.parseLenient("text/event-stream;charset=ISO-8859-1'"); Assertions.assertNotNull(contentType); Assertions.assertEquals("text/event-stream", contentType.getMimeType()); Assertions.assertNull(contentType.getCharset()); Assertions.assertEquals("ISO-8859-1'", contentType.getParameter("charset")); } @Test void testParseLenientIgnoresUnsupportedCharset() { final ContentType contentType = ContentType.parseLenient("text/plain; charset=__no_such_charset__"); Assertions.assertNotNull(contentType); Assertions.assertNull(contentType.getCharset()); } } httpcore5/src/test/java/org/apache/hc/core5/http/TestHttpExceptions.java0100644 0000000 0000000 00000007531 00000250600 025257 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Simple tests for various HTTP exception classes. */ class TestHttpExceptions { private static final String CLEAN_MESSAGE = "[0x00]Hello[0x06][0x07][0x08][0x09][0x0a][0x0b][0x0c][0x0d][0x0e][0x0f]World"; private static final String nonPrintableMessage = String.valueOf( new char[] { 1, 'H', 'e', 'l', 'l', 'o', 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 'W', 'o', 'r', 'l', 'd' }); @Test void testConstructor() { final Throwable cause = new Exception(); Assertions.assertDoesNotThrow(() -> new HttpException()); Assertions.assertDoesNotThrow(() -> new HttpException("Oppsie")); Assertions.assertDoesNotThrow(() -> new HttpException("Oppsie", cause)); Assertions.assertDoesNotThrow(() -> new ProtocolException()); Assertions.assertDoesNotThrow(() -> new ProtocolException("Oppsie")); Assertions.assertDoesNotThrow(() -> new ProtocolException("Oppsie", cause)); Assertions.assertDoesNotThrow(() -> new NoHttpResponseException("Oppsie")); Assertions.assertDoesNotThrow(() -> new ConnectionClosedException("Oppsie")); Assertions.assertDoesNotThrow(() -> new MethodNotSupportedException("Oppsie")); Assertions.assertDoesNotThrow(() -> new MethodNotSupportedException("Oppsie", cause)); Assertions.assertDoesNotThrow(() -> new UnsupportedHttpVersionException()); Assertions.assertDoesNotThrow(() -> new UnsupportedHttpVersionException("Oppsie")); } @Test void testNonPrintableCharactersInConnectionClosedException() { Assertions.assertEquals(CLEAN_MESSAGE, new ConnectionClosedException(nonPrintableMessage).getMessage()); } @Test void testNonPrintableCharactersInHttpException() { Assertions.assertEquals(CLEAN_MESSAGE, new HttpException(nonPrintableMessage).getMessage()); } @Test void testNonPrintableCharactersInMethodNotSupportedException() { Assertions.assertEquals(CLEAN_MESSAGE, new MethodNotSupportedException(nonPrintableMessage).getMessage()); } @Test void testNonPrintableCharactersInNoHttpResponseException() { Assertions.assertEquals(CLEAN_MESSAGE, new NoHttpResponseException(nonPrintableMessage).getMessage()); } @Test void testNonPrintableCharactersInProtocolException() { Assertions.assertEquals(CLEAN_MESSAGE, new ProtocolException(nonPrintableMessage).getMessage()); } @Test void testNonPrintableCharactersInUnsupportedHttpVersionException() { Assertions.assertEquals(CLEAN_MESSAGE, new UnsupportedHttpVersionException(nonPrintableMessage).getMessage()); } } httpcore5/src/test/java/org/apache/hc/core5/http/TestHttpHost.java0100644 0000000 0000000 00000027205 00000250600 024053 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link HttpHost}. * */ class TestHttpHost { @Test void testConstructor() { final HttpHost host1 = new HttpHost("somehost"); Assertions.assertEquals("somehost", host1.getHostName()); Assertions.assertEquals(-1, host1.getPort()); Assertions.assertEquals("http", host1.getSchemeName()); final HttpHost host2 = new HttpHost("somehost", 8080); Assertions.assertEquals("somehost", host2.getHostName()); Assertions.assertEquals(8080, host2.getPort()); Assertions.assertEquals("http", host2.getSchemeName()); final HttpHost host3 = new HttpHost("somehost", -1); Assertions.assertEquals("somehost", host3.getHostName()); Assertions.assertEquals(-1, host3.getPort()); Assertions.assertEquals("http", host3.getSchemeName()); final HttpHost host4 = new HttpHost("https", "somehost", 443); Assertions.assertEquals("somehost", host4.getHostName()); Assertions.assertEquals(443, host4.getPort()); Assertions.assertEquals("https", host4.getSchemeName()); final HttpHost host5 = new HttpHost("https", "somehost"); Assertions.assertEquals("somehost", host5.getHostName()); Assertions.assertEquals(-1, host5.getPort()); Assertions.assertEquals("https", host5.getSchemeName()); Assertions.assertThrows(NullPointerException.class, () -> new HttpHost(null, (String) null, -1)); Assertions.assertThrows(IllegalArgumentException.class, () -> new HttpHost(null, " ", -1)); Assertions.assertThrows(NullPointerException.class, () -> new HttpHost(null, (InetAddress) null, -1)); } @Test void testHashCode() throws Exception { final HttpHost host1 = new HttpHost("http", "somehost", 8080); final HttpHost host2 = new HttpHost("http", "somehost", 80); final HttpHost host3 = new HttpHost("http", "someotherhost", 8080); final HttpHost host4 = new HttpHost("http", "somehost", 80); final HttpHost host5 = new HttpHost("http", "SomeHost", 80); final HttpHost host6 = new HttpHost("myhttp", "SomeHost", 80); final HttpHost host7 = new HttpHost( "http", InetAddress.getByAddress("127.0.0.1", new byte[] {127,0,0,1}), 80); final HttpHost host8 = new HttpHost("http", "127.0.0.1", 80); final HttpHost host9 = new HttpHost( "http", InetAddress.getByAddress("somehost",new byte[] {127,0,0,1}), 80); final HttpHost host10 = new HttpHost( "http", InetAddress.getByAddress(new byte[] {127,0,0,1}), "somehost", 80); final HttpHost host11 = new HttpHost( "http", InetAddress.getByAddress("someotherhost",new byte[] {127,0,0,1}), 80); Assertions.assertEquals(host1.hashCode(), host1.hashCode()); Assertions.assertNotEquals(host1.hashCode(), host2.hashCode()); Assertions.assertNotEquals(host1.hashCode(), host3.hashCode()); Assertions.assertEquals(host2.hashCode(), host4.hashCode()); Assertions.assertEquals(host2.hashCode(), host5.hashCode()); Assertions.assertNotEquals(host5.hashCode(), host6.hashCode()); Assertions.assertNotEquals(host7.hashCode(), host8.hashCode()); Assertions.assertNotEquals(host8.hashCode(), host9.hashCode()); Assertions.assertEquals(host9.hashCode(), host10.hashCode()); Assertions.assertNotEquals(host10.hashCode(), host11.hashCode()); Assertions.assertNotEquals(host9.hashCode(), host11.hashCode()); } @Test void testEquals() throws Exception { final HttpHost host1 = new HttpHost("http", "somehost", 8080); final HttpHost host2 = new HttpHost("http", "somehost", 80); final HttpHost host3 = new HttpHost("http", "someotherhost", 8080); final HttpHost host4 = new HttpHost("http", "somehost", 80); final HttpHost host5 = new HttpHost("http", "SomeHost", 80); final HttpHost host6 = new HttpHost("myhttp", "SomeHost", 80); final HttpHost host7 = new HttpHost( "http", InetAddress.getByAddress("127.0.0.1", new byte[] {127,0,0,1}), 80); final HttpHost host8 = new HttpHost("http", "127.0.0.1", 80); final HttpHost host9 = new HttpHost( "http", InetAddress.getByAddress("somehost", new byte[] {127,0,0,1}), 80); final HttpHost host10 = new HttpHost( "http", InetAddress.getByAddress(new byte[] {127,0,0,1}), "somehost", 80); final HttpHost host11 = new HttpHost( "http", InetAddress.getByAddress("someotherhost",new byte[] {127,0,0,1}), 80); Assertions.assertEquals(host1, host1); Assertions.assertNotEquals(host1, host2); Assertions.assertNotEquals(host1, host3); Assertions.assertEquals(host2, host4); Assertions.assertEquals(host2, host5); Assertions.assertNotEquals(host5, host6); Assertions.assertNotEquals(host7, host8); Assertions.assertNotEquals(host7, host9); Assertions.assertNotEquals(null, host1); Assertions.assertNotEquals("http://somehost", host1); Assertions.assertNotEquals("http://somehost", host9); Assertions.assertNotEquals(host8, host9); Assertions.assertEquals(host9, host10); Assertions.assertNotEquals(host9, host11); } @Test void testToString() throws Exception { final HttpHost host1 = new HttpHost("somehost"); Assertions.assertEquals("http://somehost", host1.toString()); final HttpHost host2 = new HttpHost("somehost", -1); Assertions.assertEquals("http://somehost", host2.toString()); final HttpHost host3 = new HttpHost("somehost", -1); Assertions.assertEquals("http://somehost", host3.toString()); final HttpHost host4 = new HttpHost("somehost", 8888); Assertions.assertEquals("http://somehost:8888", host4.toString()); final HttpHost host5 = new HttpHost("myhttp", "somehost", -1); Assertions.assertEquals("myhttp://somehost", host5.toString()); final HttpHost host6 = new HttpHost("myhttp", "somehost", 80); Assertions.assertEquals("myhttp://somehost:80", host6.toString()); final HttpHost host7 = new HttpHost( "http", InetAddress.getByAddress("127.0.0.1", new byte[] {127,0,0,1}), 80); Assertions.assertEquals("http://127.0.0.1:80", host7.toString()); final HttpHost host9 = new HttpHost( "http", InetAddress.getByAddress("somehost", new byte[] {127,0,0,1}), 80); Assertions.assertEquals("http://somehost:80", host9.toString()); } @Test void testToHostString() { final HttpHost host1 = new HttpHost("somehost"); Assertions.assertEquals("somehost", host1.toHostString()); final HttpHost host2 = new HttpHost("somehost"); Assertions.assertEquals("somehost", host2.toHostString()); final HttpHost host3 = new HttpHost("somehost", -1); Assertions.assertEquals("somehost", host3.toHostString()); final HttpHost host4 = new HttpHost("somehost", 8888); Assertions.assertEquals("somehost:8888", host4.toHostString()); } @Test void testSerialization() throws Exception { final HttpHost orig = new HttpHost("https", "somehost", 8080); final ByteArrayOutputStream outbuffer = new ByteArrayOutputStream(); try (ObjectOutputStream outStream = new ObjectOutputStream(outbuffer)) { outStream.writeObject(orig); } final byte[] raw = outbuffer.toByteArray(); final ByteArrayInputStream inBuffer = new ByteArrayInputStream(raw); final ObjectInputStream inStream = new ObjectInputStream(inBuffer); final HttpHost clone = (HttpHost) inStream.readObject(); Assertions.assertEquals(orig, clone); } @Test void testCreateFromString() throws Exception { Assertions.assertEquals(new HttpHost("https", "somehost", 8080), HttpHost.create("https://somehost:8080")); Assertions.assertEquals(new HttpHost("https", "somehost", 8080), HttpHost.create("HttpS://SomeHost:8080")); Assertions.assertEquals(new HttpHost(null, "somehost", 1234), HttpHost.create("somehost:1234")); Assertions.assertEquals(new HttpHost(null, "somehost", -1), HttpHost.create("somehost")); } @Test void testCreateFromURI() { Assertions.assertEquals(new HttpHost("https", "somehost", 8080), HttpHost.create(URI.create("https://somehost:8080"))); Assertions.assertEquals(new HttpHost("https", "somehost", 8080), HttpHost.create(URI.create("HttpS://SomeHost:8080"))); Assertions.assertEquals(new HttpHost("https", "somehost", 8080), HttpHost.create(URI.create("HttpS://SomeHost:8080/foo"))); } @Test void testCreateFromStringInvalid() { Assertions.assertThrows(URISyntaxException.class, () -> HttpHost.create(" host ")); Assertions.assertThrows(URISyntaxException.class, () -> HttpHost.create("host :8080")); Assertions.assertThrows(IllegalArgumentException.class, () -> HttpHost.create("")); } @Test void testIpv6HostAndPort() throws Exception { final HttpHost host = HttpHost.create("[::1]:80"); Assertions.assertEquals("http", host.getSchemeName()); Assertions.assertEquals("::1", host.getHostName()); Assertions.assertEquals(80, host.getPort()); } @Test void testIpv6HostAndPortWithScheme() throws Exception { final HttpHost host = HttpHost.create("https://[::1]:80"); Assertions.assertEquals("https", host.getSchemeName()); Assertions.assertEquals("::1", host.getHostName()); Assertions.assertEquals(80, host.getPort()); } @Test void testIpv6HostAndPortWithoutBrackets() { Assertions.assertThrows(URISyntaxException.class, () -> HttpHost.create("::1:80")); } @Test void testIpv6HostWithoutPort() { Assertions.assertThrows(URISyntaxException.class, () -> HttpHost.create("::1")); } @Test void testIpv6HostToString() { Assertions.assertEquals("http://[::1]:80", new HttpHost("::1", 80).toString()); Assertions.assertEquals("http://[::1]", new HttpHost("::1", -1).toString()); } } httpcore5/src/test/java/org/apache/hc/core5/http/TestHttpVersion.java0100644 0000000 0000000 00000013702 00000250600 024560 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Test cases for HTTP version class */ class TestHttpVersion { @Test void testEqualsMajorMinor() { Assertions.assertTrue(HttpVersion.HTTP_0_9.equals(0, 9)); Assertions.assertTrue(HttpVersion.HTTP_1_0.equals(1, 0)); Assertions.assertTrue(HttpVersion.HTTP_1_1.equals(1, 1)); Assertions.assertTrue(HttpVersion.HTTP_2.equals(2, 0)); Assertions.assertTrue(HttpVersion.HTTP_2_0.equals(2, 0)); // Assertions.assertFalse(HttpVersion.HTTP_0_9.equals(2, 0)); } @Test void testGet() { Assertions.assertEquals(HttpVersion.HTTP_0_9, HttpVersion.get(0, 9)); Assertions.assertEquals(HttpVersion.HTTP_1_0, HttpVersion.get(1, 0)); Assertions.assertEquals(HttpVersion.HTTP_1_1, HttpVersion.get(1, 1)); Assertions.assertEquals(HttpVersion.HTTP_2_0, HttpVersion.get(2, 0)); Assertions.assertEquals(HttpVersion.HTTP_2, HttpVersion.get(2, 0)); Assertions.assertNotEquals(HttpVersion.HTTP_2_0, HttpVersion.get(2, 1)); // Assertions.assertSame(HttpVersion.HTTP_1_0, HttpVersion.get(1, 0)); Assertions.assertSame(HttpVersion.HTTP_1_1, HttpVersion.get(1, 1)); Assertions.assertSame(HttpVersion.HTTP_2_0, HttpVersion.get(2, 0)); Assertions.assertSame(HttpVersion.HTTP_2, HttpVersion.get(2, 0)); Assertions.assertNotSame(HttpVersion.HTTP_2_0, HttpVersion.get(2, 1)); Assertions.assertNotSame(HttpVersion.HTTP_0_9, HttpVersion.get(0, 9)); } @SuppressWarnings("unused") @Test void testHttpVersionInvalidConstructorInput() { Assertions.assertThrows(IllegalArgumentException.class, () -> new HttpVersion(-1, -1)); Assertions.assertThrows(IllegalArgumentException.class, () -> new HttpVersion(0, -1)); } @Test void testHttpVersionEquality() { final HttpVersion ver1 = new HttpVersion(1, 1); final HttpVersion ver2 = new HttpVersion(1, 1); Assertions.assertEquals(ver1.hashCode(), ver2.hashCode()); Assertions.assertEquals(ver1, ver1); Assertions.assertEquals(ver1, ver2); Assertions.assertEquals(ver1, ver1); Assertions.assertEquals(ver1, ver2); Assertions.assertNotEquals(ver1, Float.valueOf(1.1f)); Assertions.assertEquals(HttpVersion.HTTP_0_9, new HttpVersion(0, 9)); Assertions.assertEquals(HttpVersion.HTTP_1_0, new HttpVersion(1, 0)); Assertions.assertEquals(HttpVersion.HTTP_1_1, new HttpVersion(1, 1)); Assertions.assertNotEquals(HttpVersion.HTTP_1_0, new HttpVersion(1, 1)); Assertions.assertEquals(HttpVersion.HTTP_0_9, new ProtocolVersion("HTTP", 0, 9)); Assertions.assertEquals(HttpVersion.HTTP_1_0, new ProtocolVersion("HTTP", 1, 0)); Assertions.assertEquals(HttpVersion.HTTP_1_1, new ProtocolVersion("HTTP", 1, 1)); Assertions.assertNotEquals(HttpVersion.HTTP_1_1, new ProtocolVersion("http", 1, 1)); Assertions.assertEquals(HttpVersion.HTTP_0_9, new ProtocolVersion("HTTP", 0, 9)); Assertions.assertEquals(HttpVersion.HTTP_1_0, new ProtocolVersion("HTTP", 1, 0)); Assertions.assertEquals(HttpVersion.HTTP_1_1, new ProtocolVersion("HTTP", 1, 1)); Assertions.assertNotEquals(HttpVersion.HTTP_1_1, new ProtocolVersion("http", 1, 1)); } @Test void testHttpVersionComparison() { Assertions.assertTrue(HttpVersion.HTTP_0_9.lessEquals(HttpVersion.HTTP_1_1)); Assertions.assertTrue(HttpVersion.HTTP_0_9.greaterEquals(HttpVersion.HTTP_0_9)); Assertions.assertFalse(HttpVersion.HTTP_0_9.greaterEquals(HttpVersion.HTTP_1_0)); Assertions.assertTrue(HttpVersion.HTTP_1_0.compareToVersion(HttpVersion.HTTP_1_1) < 0); Assertions.assertTrue(HttpVersion.HTTP_1_0.compareToVersion(HttpVersion.HTTP_0_9) > 0); Assertions.assertEquals(0, HttpVersion.HTTP_1_0.compareToVersion(HttpVersion.HTTP_1_0)); } @Test void testSerialization() throws Exception { final HttpVersion orig = HttpVersion.HTTP_1_1; final ByteArrayOutputStream outbuffer = new ByteArrayOutputStream(); try (final ObjectOutputStream outStream = new ObjectOutputStream(outbuffer)) { outStream.writeObject(orig); outStream.close(); final byte[] raw = outbuffer.toByteArray(); final ByteArrayInputStream inBuffer = new ByteArrayInputStream(raw); final ObjectInputStream inStream = new ObjectInputStream(inBuffer); final HttpVersion clone = (HttpVersion) inStream.readObject(); Assertions.assertEquals(orig, clone); } } } httpcore5/src/test/java/org/apache/hc/core5/http/TestProtocolVersion.java0100644 0000000 0000000 00000007331 00000250600 025443 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import static org.hamcrest.MatcherAssert.assertThat; import org.apache.hc.core5.util.Tokenizer; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestProtocolVersion { private static final ProtocolVersion PROTOCOL_VERSION_0_0 = new ProtocolVersion("a", 0, 0); private static final ProtocolVersion PROTOCOL_VERSION_1_0 = new ProtocolVersion("b", 1, 0); private static final ProtocolVersion PROTOCOL_VERSION_1_2 = new ProtocolVersion("c", 1, 2); @Test void testEqualsMajorMinor() { Assertions.assertTrue(PROTOCOL_VERSION_0_0.equals(0, 0)); Assertions.assertTrue(PROTOCOL_VERSION_1_0.equals(1, 0)); Assertions.assertTrue(PROTOCOL_VERSION_1_2.equals(1, 2)); // Assertions.assertFalse(PROTOCOL_VERSION_1_2.equals(2, 0)); } @Test void testParseBasic() throws Exception { assertThat(ProtocolVersion.parse("PROTO/1"), CoreMatchers.equalTo(new ProtocolVersion("PROTO", 1, 0))); assertThat(ProtocolVersion.parse("PROTO/1.1"), CoreMatchers.equalTo(new ProtocolVersion("PROTO", 1, 1))); assertThat(ProtocolVersion.parse("PROTO/1.2"), CoreMatchers.equalTo(new ProtocolVersion("PROTO", 1, 2))); assertThat(ProtocolVersion.parse("PROTO/1.3 "), CoreMatchers.equalTo(new ProtocolVersion("PROTO", 1, 3))); assertThat(ProtocolVersion.parse("PROTO/000.0000 "), CoreMatchers.equalTo(new ProtocolVersion("PROTO", 0, 0))); assertThat(ProtocolVersion.parse("PROTO/22.356"), CoreMatchers.equalTo(new ProtocolVersion("PROTO", 22, 356))); } @Test void testParseBuffer() throws Exception { final Tokenizer.Cursor cursor = new Tokenizer.Cursor(1, 13); assertThat(ProtocolVersion.parse(" PROTO/1.2,0000", cursor, Tokenizer.delimiters(',')), CoreMatchers.equalTo(new ProtocolVersion("PROTO", 1, 2))); assertThat(cursor.getPos(), CoreMatchers.equalTo(10)); } @Test void testParseFailure() { Assertions.assertThrows(ParseException.class, () -> ProtocolVersion.parse("/1")); Assertions.assertThrows(ParseException.class, () -> ProtocolVersion.parse(" /1")); Assertions.assertThrows(ParseException.class, () -> ProtocolVersion.parse("PROTO/")); Assertions.assertThrows(ParseException.class, () -> ProtocolVersion.parse("PROTO/1A")); Assertions.assertThrows(ParseException.class, () -> ProtocolVersion.parse("PROTO/1.A")); Assertions.assertThrows(ParseException.class, () -> ProtocolVersion.parse("PROTO/1.1 huh?")); } } httpcore5/src/test/java/org/apache/hc/core5/http/TestProtocolVersionParser.java0100644 0000000 0000000 00000013247 00000250600 026623 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import static org.hamcrest.MatcherAssert.assertThat; import org.apache.hc.core5.http.message.ParserCursor; import org.apache.hc.core5.util.Tokenizer; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** * Unit tests for {@link ProtocolVersionParser}. */ class TestProtocolVersionParser { private ProtocolVersionParser impl; @BeforeEach void setup() { impl = new ProtocolVersionParser(); } public ProtocolVersion parseStr(final String protocol, final String s) throws ParseException { return impl.parse(protocol, null, s, new ParserCursor(0, s.length()), null); } public ProtocolVersion parseStr(final String protocol, final String s, final Tokenizer.Delimiter delimiterPredicate) throws ParseException { return impl.parse(protocol, null, s, new ParserCursor(0, s.length()), delimiterPredicate); } public ProtocolVersion parseStr(final String protocol, final String s, final Tokenizer.Cursor cursor, final Tokenizer.Delimiter delimiterPredicate) throws ParseException { return impl.parse(protocol, null, s, cursor, delimiterPredicate); } public ProtocolVersion parseStr(final String s) throws ParseException { return impl.parse(s, new ParserCursor(0, s.length()), null); } public ProtocolVersion parseStr(final String s, final Tokenizer.Delimiter delimiterPredicate) throws ParseException { return impl.parse(s, new ParserCursor(0, s.length()), delimiterPredicate); } @Test void testParseVersion() throws Exception { Assertions.assertEquals(new ProtocolVersion("PROTO", 1, 0), parseStr("PROTO", "1 ")); Assertions.assertEquals(new ProtocolVersion("PROTO", 1, 1), parseStr("PROTO", "1.1 ")); Assertions.assertEquals(new ProtocolVersion("PROTO", 1, 20), parseStr("PROTO", "1.020 ")); Assertions.assertEquals(new ProtocolVersion("PROTO", 22, 356), parseStr("PROTO", "22.356 ")); Assertions.assertEquals(new ProtocolVersion("PROTO", 1, 0), parseStr("PROTO", "1, ", Tokenizer.delimiters(','))); Assertions.assertEquals(new ProtocolVersion("PROTO", 1, 1), parseStr("PROTO", "1.1; ", Tokenizer.delimiters(',', ';'))); Assertions.assertEquals(new ProtocolVersion("PROTO", 1, 200), parseStr("PROTO", "1.200 blah; ", Tokenizer.delimiters(','))); } @Test void testParseVersionWithCursor() throws Exception { final Tokenizer.Cursor cursor = new Tokenizer.Cursor(2, 13); Assertions.assertEquals(new ProtocolVersion("PROTO", 1, 20), parseStr("PROTO", " 1.20,0000,00000", cursor, Tokenizer.delimiters(','))); assertThat(cursor.getPos(), CoreMatchers.equalTo(6)); } @Test void testParseProtocolVersion() throws Exception { Assertions.assertEquals(new ProtocolVersion("PROTO", 1, 0), parseStr("PROTO/1 ")); Assertions.assertEquals(new ProtocolVersion("PROTO", 1, 1), parseStr("PROTO/1.1 ")); Assertions.assertEquals(new ProtocolVersion("PROTO", 1, 20), parseStr("PROTO/1.020 ")); Assertions.assertEquals(new ProtocolVersion("PROTO", 22, 356), parseStr("PROTO/22.356 ")); Assertions.assertEquals(new ProtocolVersion("PROTO", 1, 0), parseStr("PROTO/1, ", Tokenizer.delimiters(','))); Assertions.assertEquals(new ProtocolVersion("PROTO", 1, 1), parseStr("PROTO/1.1; ", Tokenizer.delimiters(',', ';'))); Assertions.assertEquals(new ProtocolVersion("PROTO", 1, 200), parseStr("PROTO/1.200 blah; ", Tokenizer.delimiters(','))); } @Test void testParseFailures() { Assertions.assertThrows(ParseException.class, () -> parseStr("PROTO", "blah")); Assertions.assertThrows(ParseException.class, () -> parseStr("PROTO", "1.blah")); Assertions.assertThrows(ParseException.class, () -> parseStr("PROTO", "1A.0")); Assertions.assertThrows(ParseException.class, () -> parseStr("PROTO", "")); Assertions.assertThrows(ParseException.class, () -> parseStr("")); Assertions.assertThrows(ParseException.class, () -> parseStr(" ")); Assertions.assertThrows(ParseException.class, () -> parseStr(" /1.0")); Assertions.assertThrows(ParseException.class, () -> parseStr(" / ")); Assertions.assertThrows(ParseException.class, () -> parseStr("PROTO")); Assertions.assertThrows(ParseException.class, () -> parseStr("PROTO/")); Assertions.assertThrows(ParseException.class, () -> parseStr("PROTO/ 1.0")); } } httpcore5/src/test/java/org/apache/hc/core5/http/WritableByteChannelMock.java0100644 0000000 0000000 00000007506 00000250600 026140 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.WritableByteChannel; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; public class WritableByteChannelMock implements WritableByteChannel { private final int capacityLimit; private int capacityUsed; private ByteBuffer buf; private boolean closed; public WritableByteChannelMock(final int initialSize, final int capacityLimit) { this.buf = ByteBuffer.allocate(initialSize); this.capacityLimit = capacityLimit; } public WritableByteChannelMock(final int initialSize) { this(initialSize, 0); } private void expandCapacity(final int capacity) { final ByteBuffer oldbuffer = this.buf; this.buf = ByteBuffer.allocate(capacity); oldbuffer.flip(); this.buf.put(oldbuffer); } private void ensureCapacity(final int requiredCapacity) { if (requiredCapacity > this.buf.capacity()) { expandCapacity(requiredCapacity); } } @Override public int write(final ByteBuffer src) throws IOException { if (this.closed) { throw new ClosedChannelException(); } final int len = src.remaining(); ensureCapacity(this.buf.position() + len); if (this.capacityLimit > 0) { final int chunk = Math.min(this.capacityLimit - this.capacityUsed, len); if (chunk > 0) { final int limit = src.limit(); src.limit(src.position() + chunk); this.buf.put(src); src.limit(limit); this.capacityUsed += chunk; return chunk; } return 0; } this.buf.put(src); return len; } @Override public boolean isOpen() { return !this.closed; } @Override public void close() throws IOException { this.closed = true; } public void flush() { this.capacityUsed = 0; } public void reset() { this.capacityUsed = 0; this.buf.clear(); } public byte[] toByteArray() { final ByteBuffer dup = this.buf.duplicate(); dup.flip(); final byte[] bytes = new byte[dup.remaining()]; dup.get(bytes); return bytes; } public String dump(final Charset charset) throws CharacterCodingException { this.buf.flip(); final CharBuffer charBuffer = charset.newDecoder().decode(this.buf); this.buf.compact(); return charBuffer.toString(); } } httpcore5/src/test/java/org/apache/hc/core5/http/config/0040755 0000000 0000000 00000000000 00000250600 022035 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/config/TestNamedElementChain.java0100644 0000000 0000000 00000012241 00000250600 027036 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.config; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.CoreMatchers.is; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; /** * Tests for {@link NamedElementChain}. */ class TestNamedElementChain { @Test void testBasics() { final NamedElementChain list = new NamedElementChain<>(); assertThat(list.getFirst(), CoreMatchers.nullValue()); assertThat(list.getLast(), CoreMatchers.nullValue()); final NamedElementChain.Node nodeA = list.addFirst('a', "a"); assertThat(list.getFirst(), CoreMatchers.sameInstance(nodeA)); assertThat(list.getLast(), CoreMatchers.sameInstance(nodeA)); final NamedElementChain.Node nodeB = list.addLast('b', "b"); assertThat(list.getFirst(), CoreMatchers.sameInstance(nodeA)); assertThat(list.getLast(), CoreMatchers.sameInstance(nodeB)); final NamedElementChain.Node nodeZ = list.addLast('z', "z"); assertThat(list.getFirst(), CoreMatchers.sameInstance(nodeA)); assertThat(list.getLast(), CoreMatchers.sameInstance(nodeZ)); assertThat(nodeA.getPrevious(), CoreMatchers.nullValue()); assertThat(nodeA.getNext(), CoreMatchers.sameInstance(nodeB)); assertThat(nodeB.getPrevious(), CoreMatchers.sameInstance(nodeA)); assertThat(nodeB.getNext(), CoreMatchers.sameInstance(nodeZ)); assertThat(nodeZ.getPrevious(), CoreMatchers.sameInstance(nodeB)); assertThat(nodeZ.getNext(), CoreMatchers.nullValue()); final NamedElementChain.Node nodeD = list.addAfter("b", 'd', "d"); assertThat(nodeD.getPrevious(), CoreMatchers.sameInstance(nodeB)); assertThat(nodeD.getNext(), CoreMatchers.sameInstance(nodeZ)); assertThat(nodeB.getNext(), CoreMatchers.sameInstance(nodeD)); assertThat(nodeZ.getPrevious(), CoreMatchers.sameInstance(nodeD)); final NamedElementChain.Node nodeC = list.addBefore("d", 'c', "c"); assertThat(nodeC.getPrevious(), CoreMatchers.sameInstance(nodeB)); assertThat(nodeC.getNext(), CoreMatchers.sameInstance(nodeD)); assertThat(nodeB.getNext(), CoreMatchers.sameInstance(nodeC)); assertThat(nodeD.getPrevious(), CoreMatchers.sameInstance(nodeC)); assertThat(list.getSize(), CoreMatchers.equalTo(5)); assertThat(list.remove("a"), CoreMatchers.is(true)); assertThat(list.remove("z"), CoreMatchers.is(true)); assertThat(list.remove("c"), CoreMatchers.is(true)); assertThat(list.remove("c"), CoreMatchers.is(false)); assertThat(list.remove("blah"), CoreMatchers.is(false)); assertThat(list.getFirst(), CoreMatchers.sameInstance(nodeB)); assertThat(list.getLast(), CoreMatchers.sameInstance(nodeD)); assertThat(list.getSize(), CoreMatchers.equalTo(2)); assertThat(list.addBefore("blah", 'e', "e"), CoreMatchers.nullValue()); assertThat(list.getSize(), CoreMatchers.equalTo(2)); assertThat(list.addAfter("yada", 'e', "e"), CoreMatchers.nullValue()); assertThat(list.getSize(), CoreMatchers.equalTo(2)); } @Test void testFind() { final NamedElementChain list = new NamedElementChain<>(); list.addLast('c', "c"); assertThat(list.find("c"), notNullValue()); assertThat(list.find("a"), nullValue()); } @Test void testReplace() { final NamedElementChain list = new NamedElementChain<>(); list.addLast('c', "c"); final boolean found = list.replace("c",'z' ); assertThat(found, is(true)); assertThat(list.find("c").getValue(), equalTo('z')); assertThat(list.find("c").getName(), equalTo("c")); final boolean notFound = list.replace("X",'z' ); assertThat(notFound, is(false)); } } httpcore5/src/test/java/org/apache/hc/core5/http/config/TestRegistry.java0100644 0000000 0000000 00000003247 00000250600 025353 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.config; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestRegistry { @Test void testCompleted() { final Registry reg = RegistryBuilder.create().register("Stuff", "Stuff").build(); Assertions.assertEquals("Stuff", reg.lookup("Stuff")); Assertions.assertEquals("Stuff", reg.lookup("stuff")); Assertions.assertNull(reg.lookup("miss")); Assertions.assertNull(reg.lookup(null)); } } httpcore5/src/test/java/org/apache/hc/core5/http/examples/0040755 0000000 0000000 00000000000 00000250600 022406 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncClientSNIExample.java0100644 0000000 0000000 00000015135 00000250600 027355 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.examples; import java.net.InetAddress; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.bootstrap.AsyncRequesterBootstrap; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.support.BasicRequestBuilder; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.util.Timeout; /** * Example of SNI (Server Name Identification) usage with async I/O. */ public class AsyncClientSNIExample { public static void main(final String[] args) throws Exception { final IOReactorConfig ioReactorConfig = IOReactorConfig.custom() .setSoTimeout(5, TimeUnit.SECONDS) .build(); // Create and start requester final HttpAsyncRequester requester = AsyncRequesterBootstrap.bootstrap() .setIOReactorConfig(ioReactorConfig) .setStreamListener(new Http1StreamListener() { @Override public void onRequestHead(final HttpConnection connection, final HttpRequest request) { System.out.println(connection.getRemoteAddress() + " " + new RequestLine(request)); } @Override public void onResponseHead(final HttpConnection connection, final HttpResponse response) { System.out.println(connection.getRemoteAddress() + " " + new StatusLine(response)); } @Override public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) { if (keepAlive) { System.out.println(connection.getRemoteAddress() + " exchange completed (connection kept alive)"); } else { System.out.println(connection.getRemoteAddress() + " exchange completed (connection closed)"); } } }) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("HTTP requester shutting down"); requester.close(CloseMode.GRACEFUL); })); requester.start(); // Physical endpoint (google.com) final InetAddress targetAddress = InetAddress.getByName("www.google.com"); // Target host (google.ch) final HttpHost target = new HttpHost("https", targetAddress, "www.google.ch", 443); final HttpCoreContext context = HttpCoreContext.create(); final CountDownLatch latch = new CountDownLatch(1); final HttpRequest request = BasicRequestBuilder.get() .setPath("/") .build(); requester.execute( target, new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null, Timeout.ofSeconds(5), context, new FutureCallback>() { @Override public void completed(final Message message) { final HttpResponse response = message.getHead(); System.out.println(request.getRequestUri() + "->" + response.getCode()); final SSLSession sslSession = context.getSSLSession(); if (sslSession != null) { try { System.out.println("Peer: " + sslSession.getPeerPrincipal()); System.out.println("TLS protocol: " + sslSession.getProtocol()); System.out.println("TLS cipher suite: " + sslSession.getCipherSuite()); } catch (final SSLPeerUnverifiedException ignore) { } } latch.countDown(); } @Override public void failed(final Exception ex) { System.out.println(request.getRequestUri() + "->" + ex); latch.countDown(); } @Override public void cancelled() { System.out.println(request.getRequestUri() + " cancelled"); latch.countDown(); } }); latch.await(); System.out.println("Shutting down I/O reactor"); requester.initiateShutdown(); } } httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncFileServerExample.java0100644 0000000 0000000 00000020146 00000250600 027631 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.examples; import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EndpointDetails; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.nio.AsyncRequestConsumer; import org.apache.hc.core5.http.nio.AsyncServerRequestHandler; import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers; import org.apache.hc.core5.http.nio.entity.DiscardingEntityConsumer; import org.apache.hc.core5.http.nio.support.AsyncResponseBuilder; import org.apache.hc.core5.http.nio.support.BasicRequestConsumer; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpDateGenerator; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.util.TimeValue; /** * Example of asynchronous embedded HTTP/1.1 file server. */ public class AsyncFileServerExample { /** * Example command line args: {@code "c:\temp" 8080} */ public static void main(final String[] args) throws Exception { if (args.length < 1) { System.err.println("Please specify document root directory"); System.exit(1); } // Document root directory final File docRoot = new File(args[0]); int port = 8080; if (args.length >= 2) { port = Integer.parseInt(args[1]); } final IOReactorConfig config = IOReactorConfig.custom() .setSoTimeout(15, TimeUnit.SECONDS) .setTcpNoDelay(true) .build(); final HttpAsyncServer server = AsyncServerBootstrap.bootstrap() .setExceptionCallback(e -> e.printStackTrace()) .setIOReactorConfig(config) .register("*", new AsyncServerRequestHandler>() { @Override public AsyncRequestConsumer> prepare( final HttpRequest request, final EntityDetails entityDetails, final HttpContext context) throws HttpException { return new BasicRequestConsumer<>(entityDetails != null ? new DiscardingEntityConsumer<>() : null); } @Override public void handle( final Message message, final ResponseTrigger responseTrigger, final HttpContext localContext) throws HttpException, IOException { final HttpCoreContext context = HttpCoreContext.cast(localContext); final HttpRequest request = message.getHead(); final URI requestUri; try { requestUri = request.getUri(); } catch (final URISyntaxException ex) { throw new ProtocolException(ex.getMessage(), ex); } final String path = requestUri.getPath(); final File file = new File(docRoot, path); if (!file.exists()) { final String msg = "File " + file.getPath() + " not found"; println(msg); responseTrigger.submitResponse( AsyncResponseBuilder.create(HttpStatus.SC_NOT_FOUND) .setEntity("

" + msg + "

", ContentType.TEXT_HTML) .build(), context); } else if (!file.canRead() || file.isDirectory()) { final String msg = "Cannot read file " + file.getPath(); println(msg); responseTrigger.submitResponse(AsyncResponseBuilder.create(HttpStatus.SC_FORBIDDEN) .setEntity("

" + msg + "

", ContentType.TEXT_HTML) .build(), context); } else { final ContentType contentType; final String filename = TextUtils.toLowerCase(file.getName()); if (filename.endsWith(".txt")) { contentType = ContentType.TEXT_PLAIN; } else if (filename.endsWith(".html") || filename.endsWith(".htm")) { contentType = ContentType.TEXT_HTML; } else if (filename.endsWith(".xml")) { contentType = ContentType.TEXT_XML; } else { contentType = ContentType.DEFAULT_BINARY; } final EndpointDetails endpoint = context.getEndpointDetails(); println(endpoint + " | serving file " + file.getPath()); responseTrigger.submitResponse( AsyncResponseBuilder.create(HttpStatus.SC_OK) .setEntity(AsyncEntityProducers.create(file, contentType)) .build(), context); } } }) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { println("HTTP server shutting down"); server.close(CloseMode.GRACEFUL); })); server.start(); final Future future = server.listen(new InetSocketAddress(port), URIScheme.HTTP); final ListenerEndpoint listenerEndpoint = future.get(); println("Listening on " + listenerEndpoint.getAddress()); server.awaitShutdown(TimeValue.MAX_VALUE); } static void println(final String msg) { System.out.println(HttpDateGenerator.INSTANCE.getCurrentDate() + " | " + msg); } } httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncFullDuplexClientExample.java0100644 0000000 0000000 00000017500 00000250600 031006 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.examples; import java.io.IOException; import java.net.URI; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.HttpProcessors; import org.apache.hc.core5.http.impl.bootstrap.AsyncRequesterBootstrap; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; import org.apache.hc.core5.http.nio.AsyncRequestProducer; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.RequestChannel; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.support.AsyncRequestBuilder; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.util.Timeout; /** * Example of full-duplex, streaming HTTP message exchanges with an asynchronous HTTP/1.1 requester. */ public class AsyncFullDuplexClientExample { public static void main(final String[] args) throws Exception { final IOReactorConfig ioReactorConfig = IOReactorConfig.custom() .setSoTimeout(5, TimeUnit.SECONDS) .build(); // Create and start requester // Disable 'Expect: Continue' handshake some servers cannot handle well final HttpAsyncRequester requester = AsyncRequesterBootstrap.bootstrap() .setIOReactorConfig(ioReactorConfig) .setHttpProcessor(HttpProcessors.customClient(null).addLast((HttpRequestInterceptor) (request, entity, context) -> request.removeHeaders(HttpHeaders.EXPECT)).build()) .setStreamListener(new Http1StreamListener() { @Override public void onRequestHead(final HttpConnection connection, final HttpRequest request) { System.out.println(connection.getRemoteAddress() + " " + new RequestLine(request)); } @Override public void onResponseHead(final HttpConnection connection, final HttpResponse response) { System.out.println(connection.getRemoteAddress() + " " + new StatusLine(response)); } @Override public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) { if (keepAlive) { System.out.println(connection.getRemoteAddress() + " exchange completed (connection kept alive)"); } else { System.out.println(connection.getRemoteAddress() + " exchange completed (connection closed)"); } } }) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("HTTP requester shutting down"); requester.close(CloseMode.GRACEFUL); })); requester.start(); final URI requestUri = new URI("http://httpbin.org/post"); final AsyncRequestProducer requestProducer = AsyncRequestBuilder.post(requestUri) .setEntity("stuff") .build(); final BasicResponseConsumer responseConsumer = new BasicResponseConsumer<>( new StringAsyncEntityConsumer()); final CountDownLatch latch = new CountDownLatch(1); requester.execute(new AsyncClientExchangeHandler() { @Override public void releaseResources() { requestProducer.releaseResources(); responseConsumer.releaseResources(); latch.countDown(); } @Override public void cancel() { System.out.println(requestUri + " cancelled"); } @Override public void failed(final Exception cause) { System.out.println(requestUri + "->" + cause); } @Override public void produceRequest(final RequestChannel channel, final HttpContext httpContext) throws HttpException, IOException { requestProducer.sendRequest(channel, httpContext); } @Override public int available() { return requestProducer.available(); } @Override public void produce(final DataStreamChannel channel) throws IOException { requestProducer.produce(channel); } @Override public void consumeInformation(final HttpResponse response, final HttpContext httpContext) throws HttpException, IOException { System.out.println(requestUri + "->" + response.getCode()); } @Override public void consumeResponse(final HttpResponse response, final EntityDetails entityDetails, final HttpContext httpContext) throws HttpException, IOException { System.out.println(requestUri + "->" + response.getCode()); responseConsumer.consumeResponse(response, entityDetails, httpContext, null); } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { responseConsumer.updateCapacity(capacityChannel); } @Override public void consume(final ByteBuffer src) throws IOException { responseConsumer.consume(src); } @Override public void streamEnd(final List trailers) throws HttpException, IOException { responseConsumer.streamEnd(trailers); } }, Timeout.ofSeconds(30), HttpCoreContext.create()); latch.await(1, TimeUnit.MINUTES); System.out.println("Shutting down I/O reactor"); requester.initiateShutdown(); } } httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncFullDuplexServerExample.java0100644 0000000 0000000 00000022420 00000250600 031033 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.examples; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.util.TimeValue; /** * Example of full-duplex, streaming HTTP message exchanges with an asynchronous embedded HTTP/1.1 server. */ public class AsyncFullDuplexServerExample { public static void main(final String[] args) throws Exception { int port = 8080; if (args.length >= 1) { port = Integer.parseInt(args[0]); } final IOReactorConfig config = IOReactorConfig.custom() .setSoTimeout(15, TimeUnit.SECONDS) .setTcpNoDelay(true) .build(); final HttpAsyncServer server = AsyncServerBootstrap.bootstrap() .setExceptionCallback(e -> e.printStackTrace()) .setIOReactorConfig(config) .setStreamListener(new Http1StreamListener() { @Override public void onRequestHead(final HttpConnection connection, final HttpRequest request) { System.out.println(connection.getRemoteAddress() + " " + new RequestLine(request)); } @Override public void onResponseHead(final HttpConnection connection, final HttpResponse response) { System.out.println(connection.getRemoteAddress() + " " + new StatusLine(response)); } @Override public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) { if (keepAlive) { System.out.println(connection.getRemoteAddress() + " exchange completed (connection kept alive)"); } else { System.out.println(connection.getRemoteAddress() + " exchange completed (connection closed)"); } } }) .register("/echo", () -> new AsyncServerExchangeHandler() { ByteBuffer buffer = ByteBuffer.allocate(2048); CapacityChannel inputCapacityChannel; DataStreamChannel outputDataChannel; boolean endStream; private void ensureCapacity(final int chunk) { if (buffer.remaining() < chunk) { final ByteBuffer oldBuffer = buffer; oldBuffer.flip(); buffer = ByteBuffer.allocate(oldBuffer.remaining() + (chunk > 2048 ? chunk : 2048)); buffer.put(oldBuffer); } } @Override public void handleRequest( final HttpRequest request, final EntityDetails entityDetails, final ResponseChannel responseChannel, final HttpContext context) throws HttpException, IOException { final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK); responseChannel.sendResponse(response, entityDetails, context); } @Override public void consume(final ByteBuffer src) throws IOException { if (buffer.position() == 0) { if (outputDataChannel != null) { outputDataChannel.write(src); } } if (src.hasRemaining()) { ensureCapacity(src.remaining()); buffer.put(src); if (outputDataChannel != null) { outputDataChannel.requestOutput(); } } } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { if (buffer.hasRemaining()) { capacityChannel.update(buffer.remaining()); inputCapacityChannel = null; } else { inputCapacityChannel = capacityChannel; } } @Override public void streamEnd(final List trailers) throws IOException { endStream = true; if (buffer.position() == 0) { if (outputDataChannel != null) { outputDataChannel.endStream(); } } else { if (outputDataChannel != null) { outputDataChannel.requestOutput(); } } } @Override public int available() { return buffer.position(); } @Override public void produce(final DataStreamChannel channel) throws IOException { outputDataChannel = channel; buffer.flip(); if (buffer.hasRemaining()) { channel.write(buffer); } buffer.compact(); if (buffer.position() == 0 && endStream) { channel.endStream(); } final CapacityChannel capacityChannel = inputCapacityChannel; if (capacityChannel != null && buffer.hasRemaining()) { capacityChannel.update(buffer.remaining()); } } @Override public void failed(final Exception cause) { if (!(cause instanceof SocketException)) { cause.printStackTrace(System.out); } } @Override public void releaseResources() { } }) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("HTTP server shutting down"); server.close(CloseMode.GRACEFUL); })); server.start(); final Future future = server.listen(new InetSocketAddress(port), URIScheme.HTTP); final ListenerEndpoint listenerEndpoint = future.get(); System.out.print("Listening on " + listenerEndpoint.getAddress()); server.awaitShutdown(TimeValue.MAX_VALUE); } } ././@LongLink0100644 0000000 0000000 00000000145 00000250600 011611 Lustar 0000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncPipelinedRequestExecutionExample.javahttpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncPipelinedRequestExecutionExample.java0100644 0000000 0000000 00000014174 00000250600 032735 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.examples; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.bootstrap.AsyncRequesterBootstrap; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.support.AsyncRequestBuilder; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.util.Timeout; /** * Example of asynchronous HTTP/1.1 request execution. */ public class AsyncPipelinedRequestExecutionExample { public static void main(final String[] args) throws Exception { final IOReactorConfig ioReactorConfig = IOReactorConfig.custom() .setSoTimeout(5, TimeUnit.SECONDS) .build(); // Create and start requester final HttpAsyncRequester requester = AsyncRequesterBootstrap.bootstrap() .setIOReactorConfig(ioReactorConfig) .setStreamListener(new Http1StreamListener() { @Override public void onRequestHead(final HttpConnection connection, final HttpRequest request) { System.out.println(connection.getRemoteAddress() + " " + new RequestLine(request)); } @Override public void onResponseHead(final HttpConnection connection, final HttpResponse response) { System.out.println(connection.getRemoteAddress() + " " + new StatusLine(response)); } @Override public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) { if (keepAlive) { System.out.println(connection.getRemoteAddress() + " exchange completed (connection kept alive)"); } else { System.out.println(connection.getRemoteAddress() + " exchange completed (connection closed)"); } } }) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("HTTP requester shutting down"); requester.close(CloseMode.GRACEFUL); })); requester.start(); final HttpHost target = new HttpHost("httpbin.org"); final String[] requestUris = new String[] {"/", "/ip", "/user-agent", "/headers"}; final Future future = requester.connect(target, Timeout.ofSeconds(5)); final AsyncClientEndpoint clientEndpoint = future.get(); final CountDownLatch latch = new CountDownLatch(requestUris.length); for (final String requestUri: requestUris) { clientEndpoint.execute( AsyncRequestBuilder.get() .setHttpHost(target) .setPath(requestUri) .build(), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), new FutureCallback>() { @Override public void completed(final Message message) { latch.countDown(); final HttpResponse response = message.getHead(); final String body = message.getBody(); System.out.println(requestUri + "->" + response.getCode()); System.out.println(body); } @Override public void failed(final Exception ex) { latch.countDown(); System.out.println(requestUri + "->" + ex); } @Override public void cancelled() { latch.countDown(); System.out.println(requestUri + " cancelled"); } }); } latch.await(); // Manually release client endpoint when done !!! clientEndpoint.releaseAndDiscard(); System.out.println("Shutting down I/O reactor"); requester.initiateShutdown(); } } httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncRequestExecutionExample.java0100644 0000000 0000000 00000013454 00000250600 031103 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.examples; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.bootstrap.AsyncRequesterBootstrap; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.support.AsyncRequestBuilder; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.util.Timeout; /** * Example of asynchronous HTTP/1.1 request execution. */ public class AsyncRequestExecutionExample { public static void main(final String[] args) throws Exception { final IOReactorConfig ioReactorConfig = IOReactorConfig.custom() .setSoTimeout(5, TimeUnit.SECONDS) .build(); // Create and start requester final HttpAsyncRequester requester = AsyncRequesterBootstrap.bootstrap() .setIOReactorConfig(ioReactorConfig) .setStreamListener(new Http1StreamListener() { @Override public void onRequestHead(final HttpConnection connection, final HttpRequest request) { System.out.println(connection.getRemoteAddress() + " " + new RequestLine(request)); } @Override public void onResponseHead(final HttpConnection connection, final HttpResponse response) { System.out.println(connection.getRemoteAddress() + " " + new StatusLine(response)); } @Override public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) { if (keepAlive) { System.out.println(connection.getRemoteAddress() + " exchange completed (connection kept alive)"); } else { System.out.println(connection.getRemoteAddress() + " exchange completed (connection closed)"); } } }) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("HTTP requester shutting down"); requester.close(CloseMode.GRACEFUL); })); requester.start(); final HttpHost target = new HttpHost("httpbin.org"); final String[] requestUris = new String[] {"/", "/ip", "/user-agent", "/headers"}; final CountDownLatch latch = new CountDownLatch(requestUris.length); for (final String requestUri: requestUris) { requester.execute( AsyncRequestBuilder.get() .setHttpHost(target) .setPath(requestUri) .build(), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), Timeout.ofSeconds(5), new FutureCallback>() { @Override public void completed(final Message message) { final HttpResponse response = message.getHead(); final String body = message.getBody(); System.out.println(requestUri + "->" + response.getCode()); System.out.println(body); latch.countDown(); } @Override public void failed(final Exception ex) { System.out.println(requestUri + "->" + ex); latch.countDown(); } @Override public void cancelled() { System.out.println(requestUri + " cancelled"); latch.countDown(); } }); } latch.await(); System.out.println("Shutting down I/O reactor"); requester.initiateShutdown(); } } httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncReverseProxyExample.java0100644 0000000 0000000 00000077146 00000250600 030254 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.examples; import java.io.IOException; import java.io.InterruptedIOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.BasicEntityDetails; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.bootstrap.AsyncRequesterBootstrap; import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.impl.nio.BufferedData; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.RequestChannel; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpDateGenerator; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.pool.ConnPoolListener; import org.apache.hc.core5.pool.ConnPoolStats; import org.apache.hc.core5.pool.PoolStats; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; /** * Example of asynchronous embedded HTTP/1.1 reverse proxy with full content streaming. */ public class AsyncReverseProxyExample { private static boolean quiet; public static void main(final String[] args) throws Exception { if (args.length < 1) { System.out.println("Usage: [listener port] [--quiet]"); System.exit(1); } // Target host final HttpHost targetHost = HttpHost.create(args[0]); int port = 8080; if (args.length > 1) { port = Integer.parseInt(args[1]); } for (final String s : args) { if ("--quiet".equalsIgnoreCase(s)) { quiet = true; break; } } println("Reverse proxy to " + targetHost); final IOReactorConfig config = IOReactorConfig.custom() .setSoTimeout(1, TimeUnit.MINUTES) .build(); final HttpAsyncRequester requester = AsyncRequesterBootstrap.bootstrap() .setIOReactorConfig(config) .setConnPoolListener(new ConnPoolListener() { @Override public void onLease(final HttpHost route, final ConnPoolStats connPoolStats) { final StringBuilder buf = new StringBuilder(); buf.append("[proxy->origin] connection leased ").append(route); println(buf.toString()); } @Override public void onRelease(final HttpHost route, final ConnPoolStats connPoolStats) { final StringBuilder buf = new StringBuilder(); buf.append("[proxy->origin] connection released ").append(route); final PoolStats totals = connPoolStats.getTotalStats(); buf.append("; total kept alive: ").append(totals.getAvailable()).append("; "); buf.append("total allocated: ").append(totals.getLeased() + totals.getAvailable()); buf.append(" of ").append(totals.getMax()); println(buf.toString()); } }) .setStreamListener(new Http1StreamListener() { @Override public void onRequestHead(final HttpConnection connection, final HttpRequest request) { // empty } @Override public void onResponseHead(final HttpConnection connection, final HttpResponse response) { // empty } @Override public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) { println("[proxy<-origin] connection " + connection.getLocalAddress() + "->" + connection.getRemoteAddress() + (keepAlive ? " kept alive" : " cannot be kept alive")); } }) .setMaxTotal(100) .setDefaultMaxPerRoute(20) .create(); final HttpAsyncServer server = AsyncServerBootstrap.bootstrap() .setExceptionCallback(e -> e.printStackTrace()) .setIOReactorConfig(config) .setStreamListener(new Http1StreamListener() { @Override public void onRequestHead(final HttpConnection connection, final HttpRequest request) { // empty } @Override public void onResponseHead(final HttpConnection connection, final HttpResponse response) { // empty } @Override public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) { println("[client<-proxy] connection " + connection.getLocalAddress() + "->" + connection.getRemoteAddress() + (keepAlive ? " kept alive" : " cannot be kept alive")); } }) .register("*", () -> new IncomingExchangeHandler(targetHost, requester)) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { println("Reverse proxy shutting down"); server.close(CloseMode.GRACEFUL); requester.close(CloseMode.GRACEFUL); })); requester.start(); server.start(); server.listen(new InetSocketAddress(port), URIScheme.HTTP); println("Listening on port " + port); server.awaitShutdown(TimeValue.MAX_VALUE); } private static class ProxyBuffer extends BufferedData { ProxyBuffer(final int bufferSize) { super(bufferSize); } int write(final DataStreamChannel channel) throws IOException { setOutputMode(); if (buffer().hasRemaining()) { return channel.write(buffer()); } return 0; } } private static final AtomicLong COUNT = new AtomicLong(0); private static class ProxyExchangeState { final String id; HttpRequest request; EntityDetails requestEntityDetails; DataStreamChannel requestDataChannel; CapacityChannel requestCapacityChannel; ProxyBuffer inBuf; boolean inputEnd; HttpResponse response; EntityDetails responseEntityDetails; ResponseChannel responseMessageChannel; DataStreamChannel responseDataChannel; CapacityChannel responseCapacityChannel; ProxyBuffer outBuf; boolean outputEnd; AsyncClientEndpoint clientEndpoint; ProxyExchangeState() { this.id = String.format("%010d", COUNT.getAndIncrement()); } } private static final int INIT_BUFFER_SIZE = 4096; private static class IncomingExchangeHandler implements AsyncServerExchangeHandler { private final HttpHost targetHost; private final HttpAsyncRequester requester; private final ProxyExchangeState exchangeState; private final ReentrantLock lock; IncomingExchangeHandler(final HttpHost targetHost, final HttpAsyncRequester requester) { super(); this.targetHost = targetHost; this.requester = requester; this.exchangeState = new ProxyExchangeState(); this.lock = new ReentrantLock(); } @Override public void handleRequest( final HttpRequest incomingRequest, final EntityDetails entityDetails, final ResponseChannel responseChannel, final HttpContext httpContext) throws HttpException, IOException { lock.lock(); try { println("[client->proxy] " + exchangeState.id + " " + incomingRequest.getMethod() + " " + incomingRequest.getRequestUri()); exchangeState.request = incomingRequest; exchangeState.requestEntityDetails = entityDetails; exchangeState.inputEnd = entityDetails == null; exchangeState.responseMessageChannel = responseChannel; if (entityDetails != null) { final Header h = incomingRequest.getFirstHeader(HttpHeaders.EXPECT); if (h != null && HeaderElements.CONTINUE.equalsIgnoreCase(h.getValue())) { responseChannel.sendInformation(new BasicHttpResponse(HttpStatus.SC_CONTINUE), httpContext); } } } finally { lock.unlock(); } println("[proxy->origin] " + exchangeState.id + " request connection to " + targetHost); requester.connect(targetHost, Timeout.ofSeconds(30), null, new FutureCallback() { @Override public void completed(final AsyncClientEndpoint clientEndpoint) { println("[proxy->origin] " + exchangeState.id + " connection leased"); lock.lock(); try { exchangeState.clientEndpoint = clientEndpoint; } finally { lock.unlock(); } clientEndpoint.execute( new OutgoingExchangeHandler(targetHost, clientEndpoint, exchangeState), HttpCoreContext.create()); } @Override public void failed(final Exception cause) { final HttpResponse outgoingResponse = new BasicHttpResponse(HttpStatus.SC_SERVICE_UNAVAILABLE); outgoingResponse.addHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); final ByteBuffer msg = StandardCharsets.US_ASCII.encode(CharBuffer.wrap(cause.getMessage())); final EntityDetails exEntityDetails = new BasicEntityDetails(msg.remaining(), ContentType.TEXT_PLAIN); lock.lock(); try { exchangeState.response = outgoingResponse; exchangeState.responseEntityDetails = exEntityDetails; exchangeState.outBuf = new ProxyBuffer(1024); exchangeState.outBuf.put(msg); exchangeState.outputEnd = true; } finally { lock.unlock(); } println("[client<-proxy] " + exchangeState.id + " status " + outgoingResponse.getCode()); try { responseChannel.sendResponse(outgoingResponse, exEntityDetails, httpContext); } catch (final HttpException | IOException ignore) { // ignore } } @Override public void cancelled() { failed(new InterruptedIOException()); } }); } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { lock.lock(); try { exchangeState.requestCapacityChannel = capacityChannel; final int capacity = exchangeState.inBuf != null ? exchangeState.inBuf.capacity() : INIT_BUFFER_SIZE; if (capacity > 0) { println("[client<-proxy] " + exchangeState.id + " input capacity: " + capacity); capacityChannel.update(capacity); } } finally { lock.unlock(); } } @Override public void consume(final ByteBuffer src) throws IOException { lock.lock(); try { println("[client->proxy] " + exchangeState.id + " " + src.remaining() + " bytes received"); final DataStreamChannel dataChannel = exchangeState.requestDataChannel; if (dataChannel != null && exchangeState.inBuf != null) { if (exchangeState.inBuf.hasData()) { final int bytesWritten = exchangeState.inBuf.write(dataChannel); println("[proxy->origin] " + exchangeState.id + " " + bytesWritten + " bytes sent"); } if (!exchangeState.inBuf.hasData()) { final int bytesWritten = dataChannel.write(src); println("[proxy->origin] " + exchangeState.id + " " + bytesWritten + " bytes sent"); } } if (src.hasRemaining()) { if (exchangeState.inBuf == null) { exchangeState.inBuf = new ProxyBuffer(INIT_BUFFER_SIZE); } exchangeState.inBuf.put(src); } final int capacity = exchangeState.inBuf != null ? exchangeState.inBuf.capacity() : INIT_BUFFER_SIZE; println("[client<-proxy] " + exchangeState.id + " input capacity: " + capacity); if (dataChannel != null) { dataChannel.requestOutput(); } } finally { lock.unlock(); } } @Override public void streamEnd(final List trailers) throws HttpException, IOException { lock.lock(); try { println("[client->proxy] " + exchangeState.id + " end of input"); exchangeState.inputEnd = true; final DataStreamChannel dataChannel = exchangeState.requestDataChannel; if (dataChannel != null && (exchangeState.inBuf == null || !exchangeState.inBuf.hasData())) { println("[proxy->origin] " + exchangeState.id + " end of output"); dataChannel.endStream(); } } finally { lock.unlock(); } } @Override public int available() { lock.lock(); try { final int available = exchangeState.outBuf != null ? exchangeState.outBuf.length() : 0; println("[client<-proxy] " + exchangeState.id + " output available: " + available); return available; } finally { lock.unlock(); } } @Override public void produce(final DataStreamChannel channel) throws IOException { lock.lock(); try { println("[client<-proxy] " + exchangeState.id + " produce output"); exchangeState.responseDataChannel = channel; if (exchangeState.outBuf != null) { if (exchangeState.outBuf.hasData()) { final int bytesWritten = exchangeState.outBuf.write(channel); println("[client<-proxy] " + exchangeState.id + " " + bytesWritten + " bytes sent"); } if (exchangeState.outputEnd && !exchangeState.outBuf.hasData()) { channel.endStream(); println("[client<-proxy] " + exchangeState.id + " end of output"); } if (!exchangeState.outputEnd) { final CapacityChannel capacityChannel = exchangeState.responseCapacityChannel; if (capacityChannel != null) { final int capacity = exchangeState.outBuf.capacity(); if (capacity > 0) { println("[proxy->origin] " + exchangeState.id + " input capacity: " + capacity); capacityChannel.update(capacity); } } } } } finally { lock.unlock(); } } @Override public void failed(final Exception cause) { println("[client<-proxy] " + exchangeState.id + " " + cause.getMessage()); if (!(cause instanceof ConnectionClosedException)) { cause.printStackTrace(System.out); } lock.lock(); try { if (exchangeState.clientEndpoint != null) { exchangeState.clientEndpoint.releaseAndDiscard(); } } finally { lock.unlock(); } } @Override public void releaseResources() { lock.lock(); try { exchangeState.responseMessageChannel = null; exchangeState.responseDataChannel = null; exchangeState.requestCapacityChannel = null; } finally { lock.unlock(); } } } private static class OutgoingExchangeHandler implements AsyncClientExchangeHandler { private final static Set HOP_BY_HOP = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( TextUtils.toLowerCase(HttpHeaders.HOST), TextUtils.toLowerCase(HttpHeaders.CONTENT_LENGTH), TextUtils.toLowerCase(HttpHeaders.TRANSFER_ENCODING), TextUtils.toLowerCase(HttpHeaders.CONNECTION), TextUtils.toLowerCase(HttpHeaders.KEEP_ALIVE), TextUtils.toLowerCase(HttpHeaders.PROXY_AUTHENTICATE), TextUtils.toLowerCase(HttpHeaders.TE), TextUtils.toLowerCase(HttpHeaders.TRAILER), TextUtils.toLowerCase(HttpHeaders.UPGRADE)))); private final HttpHost targetHost; private final AsyncClientEndpoint clientEndpoint; private final ProxyExchangeState exchangeState; private final ReentrantLock lock; OutgoingExchangeHandler( final HttpHost targetHost, final AsyncClientEndpoint clientEndpoint, final ProxyExchangeState exchangeState) { this.targetHost = targetHost; this.clientEndpoint = clientEndpoint; this.exchangeState = exchangeState; this.lock = new ReentrantLock(); } @Override public void produceRequest( final RequestChannel channel, final HttpContext httpContext) throws HttpException, IOException { lock.lock(); try { final HttpRequest incomingRequest = exchangeState.request; final EntityDetails entityDetails = exchangeState.requestEntityDetails; final HttpRequest outgoingRequest = new BasicHttpRequest( incomingRequest.getMethod(), targetHost, incomingRequest.getPath()); for (final Iterator
it = incomingRequest.headerIterator(); it.hasNext(); ) { final Header header = it.next(); if (!HOP_BY_HOP.contains(TextUtils.toLowerCase(header.getName()))) { outgoingRequest.addHeader(header); } } println("[proxy->origin] " + exchangeState.id + " " + outgoingRequest.getMethod() + " " + outgoingRequest.getRequestUri()); channel.sendRequest(outgoingRequest, entityDetails, httpContext); } finally { lock.unlock(); } } @Override public int available() { lock.lock(); try { final int available = exchangeState.inBuf != null ? exchangeState.inBuf.length() : 0; println("[proxy->origin] " + exchangeState.id + " output available: " + available); return available; } finally { lock.unlock(); } } @Override public void produce(final DataStreamChannel channel) throws IOException { lock.lock(); try { println("[proxy->origin] " + exchangeState.id + " produce output"); exchangeState.requestDataChannel = channel; if (exchangeState.inBuf != null) { if (exchangeState.inBuf.hasData()) { final int bytesWritten = exchangeState.inBuf.write(channel); println("[proxy->origin] " + exchangeState.id + " " + bytesWritten + " bytes sent"); } if (exchangeState.inputEnd && !exchangeState.inBuf.hasData()) { channel.endStream(); println("[proxy->origin] " + exchangeState.id + " end of output"); } if (!exchangeState.inputEnd) { final CapacityChannel capacityChannel = exchangeState.requestCapacityChannel; if (capacityChannel != null) { final int capacity = exchangeState.inBuf.capacity(); if (capacity > 0) { println("[client<-proxy] " + exchangeState.id + " input capacity: " + capacity); capacityChannel.update(capacity); } } } } } finally { lock.unlock(); } } @Override public void consumeInformation(final HttpResponse response, final HttpContext httpContext) throws HttpException, IOException { // ignore } @Override public void consumeResponse( final HttpResponse incomingResponse, final EntityDetails entityDetails, final HttpContext httpContext) throws HttpException, IOException { lock.lock(); try { println("[proxy<-origin] " + exchangeState.id + " status " + incomingResponse.getCode()); if (entityDetails == null) { println("[proxy<-origin] " + exchangeState.id + " end of input"); } final HttpResponse outgoingResponse = new BasicHttpResponse(incomingResponse.getCode()); for (final Iterator
it = incomingResponse.headerIterator(); it.hasNext(); ) { final Header header = it.next(); if (!HOP_BY_HOP.contains(TextUtils.toLowerCase(header.getName()))) { outgoingResponse.addHeader(header); } } exchangeState.response = outgoingResponse; exchangeState.responseEntityDetails = entityDetails; exchangeState.outputEnd = entityDetails == null; final ResponseChannel responseChannel = exchangeState.responseMessageChannel; if (responseChannel != null) { // responseChannel can be null under load. responseChannel.sendResponse(outgoingResponse, entityDetails, httpContext); } println("[client<-proxy] " + exchangeState.id + " status " + outgoingResponse.getCode()); if (entityDetails == null) { println("[client<-proxy] " + exchangeState.id + " end of output"); clientEndpoint.releaseAndReuse(); } } finally { lock.unlock(); } } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { lock.lock(); try { exchangeState.responseCapacityChannel = capacityChannel; final int capacity = exchangeState.outBuf != null ? exchangeState.outBuf.capacity() : INIT_BUFFER_SIZE; if (capacity > 0) { println("[proxy->origin] " + exchangeState.id + " input capacity: " + capacity); capacityChannel.update(capacity); } } finally { lock.unlock(); } } @Override public void consume(final ByteBuffer src) throws IOException { lock.lock(); try { println("[proxy<-origin] " + exchangeState.id + " " + src.remaining() + " bytes received"); final DataStreamChannel dataChannel = exchangeState.responseDataChannel; if (dataChannel != null && exchangeState.outBuf != null) { if (exchangeState.outBuf.hasData()) { final int bytesWritten = exchangeState.outBuf.write(dataChannel); println("[client<-proxy] " + exchangeState.id + " " + bytesWritten + " bytes sent"); } if (!exchangeState.outBuf.hasData()) { final int bytesWritten = dataChannel.write(src); println("[client<-proxy] " + exchangeState.id + " " + bytesWritten + " bytes sent"); } } if (src.hasRemaining()) { if (exchangeState.outBuf == null) { exchangeState.outBuf = new ProxyBuffer(INIT_BUFFER_SIZE); } exchangeState.outBuf.put(src); } final int capacity = exchangeState.outBuf != null ? exchangeState.outBuf.capacity() : INIT_BUFFER_SIZE; println("[proxy->origin] " + exchangeState.id + " input capacity: " + capacity); if (dataChannel != null) { dataChannel.requestOutput(); } } finally { lock.unlock(); } } @Override public void streamEnd(final List trailers) throws HttpException, IOException { lock.lock(); try { println("[proxy<-origin] " + exchangeState.id + " end of input"); exchangeState.outputEnd = true; final DataStreamChannel dataChannel = exchangeState.responseDataChannel; if (dataChannel != null && (exchangeState.outBuf == null || !exchangeState.outBuf.hasData())) { println("[client<-proxy] " + exchangeState.id + " end of output"); dataChannel.endStream(); clientEndpoint.releaseAndReuse(); } } finally { lock.unlock(); } } @Override public void cancel() { clientEndpoint.releaseAndDiscard(); } @Override public void failed(final Exception cause) { println("[client<-proxy] " + exchangeState.id + " " + cause.getMessage()); if (!(cause instanceof ConnectionClosedException)) { cause.printStackTrace(System.out); } lock.lock(); try { if (exchangeState.response == null) { final int status = cause instanceof IOException ? HttpStatus.SC_SERVICE_UNAVAILABLE : HttpStatus.SC_INTERNAL_SERVER_ERROR; final HttpResponse outgoingResponse = new BasicHttpResponse(status); outgoingResponse.addHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); exchangeState.response = outgoingResponse; final ByteBuffer msg = StandardCharsets.US_ASCII.encode(CharBuffer.wrap(cause.getMessage())); final int contentLen = msg.remaining(); exchangeState.outBuf = new ProxyBuffer(1024); exchangeState.outBuf.put(msg); exchangeState.outputEnd = true; println("[client<-proxy] " + exchangeState.id + " status " + outgoingResponse.getCode()); try { final EntityDetails entityDetails = new BasicEntityDetails(contentLen, ContentType.TEXT_PLAIN); exchangeState.responseMessageChannel.sendResponse(outgoingResponse, entityDetails, null); } catch (final HttpException | IOException ignore) { // ignore } } else { exchangeState.outputEnd = true; } clientEndpoint.releaseAndDiscard(); } finally { lock.unlock(); } } @Override public void releaseResources() { lock.lock(); try { exchangeState.requestDataChannel = null; exchangeState.responseCapacityChannel = null; clientEndpoint.releaseAndDiscard(); } finally { lock.unlock(); } } } static void println(final String msg) { if (!quiet) { System.out.println(HttpDateGenerator.INSTANCE.getCurrentDate() + " | " + msg); } } } httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncServerFilterExample.java0100644 0000000 0000000 00000020265 00000250600 030201 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.examples; import java.io.IOException; import java.net.InetSocketAddress; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.impl.bootstrap.StandardFilter; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.AsyncFilterChain; import org.apache.hc.core5.http.nio.AsyncPushProducer; import org.apache.hc.core5.http.nio.AsyncRequestConsumer; import org.apache.hc.core5.http.nio.AsyncServerRequestHandler; import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.support.AbstractAsyncServerAuthFilter; import org.apache.hc.core5.http.nio.support.AsyncResponseBuilder; import org.apache.hc.core5.http.nio.support.BasicRequestConsumer; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.util.TimeValue; /** * Example of using asynchronous I/O request filters with an embedded HTTP/1.1 server. */ public class AsyncServerFilterExample { public static void main(final String[] args) throws Exception { int port = 8080; if (args.length >= 1) { port = Integer.parseInt(args[0]); } final IOReactorConfig config = IOReactorConfig.custom() .setSoTimeout(15, TimeUnit.SECONDS) .setTcpNoDelay(true) .build(); final HttpAsyncServer server = AsyncServerBootstrap.bootstrap() .setExceptionCallback(e -> e.printStackTrace()) .setIOReactorConfig(config) // Replace standard expect-continue handling with a custom auth filter .replaceFilter(StandardFilter.EXPECT_CONTINUE.name(), new AbstractAsyncServerAuthFilter(true) { @Override protected String parseChallengeResponse( final String authorizationValue, final HttpContext context) throws HttpException { return authorizationValue; } @Override protected boolean authenticate( final String challengeResponse, final URIAuthority authority, final String requestUri, final HttpContext context) { return "let me pass".equals(challengeResponse); } @Override protected String generateChallenge( final String challengeResponse, final URIAuthority authority, final String requestUri, final HttpContext context) { return "who goes there?"; } }) // Add a custom request filter at the beginning of the processing pipeline .addFilterFirst("my-filter", (request, entityDetails, context, responseTrigger, chain) -> { if (request.getRequestUri().equals("/back-door")) { responseTrigger.submitResponse( new BasicHttpResponse(HttpStatus.SC_OK), AsyncEntityProducers.create("Welcome")); return null; } return chain.proceed(request, entityDetails, context, new AsyncFilterChain.ResponseTrigger() { @Override public void sendInformation( final HttpResponse response) throws HttpException, IOException { responseTrigger.sendInformation(response); } @Override public void submitResponse( final HttpResponse response, final AsyncEntityProducer entityProducer) throws HttpException, IOException { response.addHeader("X-Filter", "My-Filter"); responseTrigger.submitResponse(response, entityProducer); } @Override public void pushPromise( final HttpRequest promise, final AsyncPushProducer responseProducer) throws HttpException, IOException { responseTrigger.pushPromise(promise, responseProducer); } }); }) // Application request handler .register("*", new AsyncServerRequestHandler>() { @Override public AsyncRequestConsumer> prepare( final HttpRequest request, final EntityDetails entityDetails, final HttpContext context) throws HttpException { return new BasicRequestConsumer<>(entityDetails != null ? new StringAsyncEntityConsumer() : null); } @Override public void handle( final Message requestMessage, final ResponseTrigger responseTrigger, final HttpContext context) throws HttpException, IOException { // do something useful responseTrigger.submitResponse( AsyncResponseBuilder.create(HttpStatus.SC_OK) .setEntity("Hello") .build(), context); } }) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("HTTP server shutting down"); server.close(CloseMode.GRACEFUL); })); server.start(); final Future future = server.listen(new InetSocketAddress(port), URIScheme.HTTP); final ListenerEndpoint listenerEndpoint = future.get(); System.out.print("Listening on " + listenerEndpoint.getAddress()); server.awaitShutdown(TimeValue.MAX_VALUE); } } httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicClientSNIExample.java0100644 0000000 0000000 00000011254 00000250600 027657 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.examples; import java.net.InetAddress; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLSession; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.bootstrap.HttpRequester; import org.apache.hc.core5.http.impl.bootstrap.RequesterBootstrap; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.util.Timeout; /** * Example of SNI (Server Name Identification) usage with classic I/O. */ public class ClassicClientSNIExample { public static void main(final String[] args) throws Exception { final HttpRequester httpRequester = RequesterBootstrap.bootstrap() .setStreamListener(new Http1StreamListener() { @Override public void onRequestHead(final HttpConnection connection, final HttpRequest request) { System.out.println(connection.getRemoteAddress() + " " + new RequestLine(request)); } @Override public void onResponseHead(final HttpConnection connection, final HttpResponse response) { System.out.println(connection.getRemoteAddress() + " " + new StatusLine(response)); } @Override public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) { if (keepAlive) { System.out.println(connection.getRemoteAddress() + " exchange completed (connection kept alive)"); } else { System.out.println(connection.getRemoteAddress() + " exchange completed (connection closed)"); } } }) .setSocketConfig(SocketConfig.custom() .setSoTimeout(5, TimeUnit.SECONDS) .build()) .create(); // Physical endpoint (google.com) final InetAddress targetAddress = InetAddress.getByName("www.google.com"); // Target host (google.ch) final HttpHost target = new HttpHost("https", targetAddress, "www.google.ch", 443); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = ClassicRequestBuilder.get() .setPath("/") .build(); try (ClassicHttpResponse response = httpRequester.execute(target, request, Timeout.ofSeconds(5), context)) { System.out.println(request.getUri() + "->" + response.getCode()); final SSLSession sslSession = context.getSSLSession(); if (sslSession != null) { System.out.println("Peer: " + sslSession.getPeerPrincipal()); System.out.println("TLS protocol: " + sslSession.getProtocol()); System.out.println("TLS cipher suite: " + sslSession.getCipherSuite()); } System.out.println("=============="); } } } httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicFileServerExample.java0100644 0000000 0000000 00000017260 00000250600 030140 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.examples; import java.io.File; import java.io.IOException; import java.net.SocketTimeoutException; import java.net.URL; import java.net.URLDecoder; import java.nio.charset.Charset; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EndpointDetails; import org.apache.hc.core5.http.ExceptionListener; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.MethodNotSupportedException; import org.apache.hc.core5.http.impl.bootstrap.HttpServer; import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.FileEntity; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.ssl.SSLContexts; import org.apache.hc.core5.util.TimeValue; /** * Example of embedded HTTP/1.1 file server using classic I/O. */ public class ClassicFileServerExample { public static void main(final String[] args) throws Exception { if (args.length < 1) { System.err.println("Please specify document root directory"); System.exit(1); } // Document root directory final String docRoot = args[0]; int port = 8080; if (args.length >= 2) { port = Integer.parseInt(args[1]); } SSLContext sslContext = null; if (port == 8443) { // Initialize SSL context final URL url = ClassicFileServerExample.class.getResource("/my.keystore"); if (url == null) { System.out.println("Keystore not found"); System.exit(1); } sslContext = SSLContexts.custom() .loadKeyMaterial(url, "secret".toCharArray(), "secret".toCharArray()) .build(); } final SocketConfig socketConfig = SocketConfig.custom() .setSoTimeout(15, TimeUnit.SECONDS) .setTcpNoDelay(true) .build(); final HttpServer server = ServerBootstrap.bootstrap() .setListenerPort(port) .setSocketConfig(socketConfig) .setSslContext(sslContext) .setExceptionListener(new ExceptionListener() { @Override public void onError(final Exception ex) { ex.printStackTrace(); } @Override public void onError(final HttpConnection conn, final Exception ex) { if (ex instanceof SocketTimeoutException) { System.err.println("Connection timed out"); } else if (ex instanceof ConnectionClosedException) { System.err.println(ex.getMessage()); } else { ex.printStackTrace(); } } }) .register("*", new HttpFileHandler(docRoot)) .create(); server.start(); Runtime.getRuntime().addShutdownHook(new Thread(() -> server.close(CloseMode.GRACEFUL))); System.out.println("Listening on port " + port); server.awaitTermination(TimeValue.MAX_VALUE); } static class HttpFileHandler implements HttpRequestHandler { private final String docRoot; public HttpFileHandler(final String docRoot) { super(); this.docRoot = docRoot; } @Override public void handle( final ClassicHttpRequest request, final ClassicHttpResponse response, final HttpContext localContext) throws HttpException, IOException { final HttpCoreContext context = HttpCoreContext.cast(localContext); final String method = request.getMethod(); if (!Method.GET.isSame(method) && !Method.HEAD.isSame(method) && !Method.POST.isSame(method)) { throw new MethodNotSupportedException(method + " method not supported"); } final String path = request.getPath(); final HttpEntity incomingEntity = request.getEntity(); if (incomingEntity != null) { final byte[] entityContent = EntityUtils.toByteArray(incomingEntity); System.out.println("Incoming incomingEntity content (bytes): " + entityContent.length); } final File file = new File(this.docRoot, URLDecoder.decode(path, "UTF-8")); if (!file.exists()) { response.setCode(HttpStatus.SC_NOT_FOUND); final String msg = "File " + file.getPath() + " not found"; final StringEntity outgoingEntity = new StringEntity( "

" + msg + "

", ContentType.create("text/html", "UTF-8")); response.setEntity(outgoingEntity); System.out.println(msg); } else if (!file.canRead() || file.isDirectory()) { response.setCode(HttpStatus.SC_FORBIDDEN); final String msg = "Cannot read file " + file.getPath(); final StringEntity outgoingEntity = new StringEntity( "

" + msg + "

", ContentType.create("text/html", "UTF-8")); response.setEntity(outgoingEntity); System.out.println(msg); } else { final EndpointDetails endpoint = context.getEndpointDetails(); response.setCode(HttpStatus.SC_OK); final FileEntity body = new FileEntity(file, ContentType.create("text/html", (Charset) null)); response.setEntity(body); System.out.println(endpoint + ": serving file " + file.getPath()); } } } } httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicGetExecutionExample.java0100644 0000000 0000000 00000010720 00000250600 030467 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.examples; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.bootstrap.HttpRequester; import org.apache.hc.core5.http.impl.bootstrap.RequesterBootstrap; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.util.Timeout; /** * Example of GET requests execution using classic I/O. */ public class ClassicGetExecutionExample { public static void main(final String[] args) throws Exception { final HttpRequester httpRequester = RequesterBootstrap.bootstrap() .setStreamListener(new Http1StreamListener() { @Override public void onRequestHead(final HttpConnection connection, final HttpRequest request) { System.out.println(connection.getRemoteAddress() + " " + new RequestLine(request)); } @Override public void onResponseHead(final HttpConnection connection, final HttpResponse response) { System.out.println(connection.getRemoteAddress() + " " + new StatusLine(response)); } @Override public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) { if (keepAlive) { System.out.println(connection.getRemoteAddress() + " exchange completed (connection kept alive)"); } else { System.out.println(connection.getRemoteAddress() + " exchange completed (connection closed)"); } } }) .setSocketConfig(SocketConfig.custom() .setSoTimeout(5, TimeUnit.SECONDS) .build()) .create(); final HttpCoreContext context = HttpCoreContext.create(); final HttpHost target = new HttpHost("httpbin.org"); final String[] requestUris = new String[] {"/", "/ip", "/user-agent", "/headers"}; for (int i = 0; i < requestUris.length; i++) { final String requestUri = requestUris[i]; final ClassicHttpRequest request = ClassicRequestBuilder.get() .setHttpHost(target) .setPath(requestUri) .build(); try (ClassicHttpResponse response = httpRequester.execute(target, request, Timeout.ofSeconds(5), context)) { System.out.println(requestUri + "->" + response.getCode()); System.out.println(EntityUtils.toString(response.getEntity())); System.out.println("=============="); } } } } httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicPostExecutionExample.java0100644 0000000 0000000 00000013136 00000250600 030701 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.examples; import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.bootstrap.HttpRequester; import org.apache.hc.core5.http.impl.bootstrap.RequesterBootstrap; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.HttpEntities; import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.util.Timeout; /** * Example of POST requests execution using classic I/O. */ public class ClassicPostExecutionExample { public static void main(final String[] args) throws Exception { final HttpRequester httpRequester = RequesterBootstrap.bootstrap() .setStreamListener(new Http1StreamListener() { @Override public void onRequestHead(final HttpConnection connection, final HttpRequest request) { System.out.println(connection.getRemoteAddress() + " " + new RequestLine(request)); } @Override public void onResponseHead(final HttpConnection connection, final HttpResponse response) { System.out.println(connection.getRemoteAddress() + " " + new StatusLine(response)); } @Override public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) { if (keepAlive) { System.out.println(connection.getRemoteAddress() + " exchange completed (connection kept alive)"); } else { System.out.println(connection.getRemoteAddress() + " exchange completed (connection closed)"); } } }) .setSocketConfig(SocketConfig.custom() .setSoTimeout(5, TimeUnit.SECONDS) .build()) .create(); final HttpCoreContext context = HttpCoreContext.create(); final HttpHost target = new HttpHost("httpbin.org"); final HttpEntity[] requestBodies = { HttpEntities.create( "This is the first test request", ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)), HttpEntities.create( "This is the second test request".getBytes(StandardCharsets.UTF_8), ContentType.APPLICATION_OCTET_STREAM), HttpEntities.create(outStream -> outStream.write(("This is the third test request " + "(streaming)").getBytes(StandardCharsets.UTF_8)), ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)), HttpEntities.create( "This is the fourth test request " + "(streaming with trailers)", ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8), new BasicHeader("trailer1","And goodbye")) }; final String requestUri = "/post"; for (int i = 0; i < requestBodies.length; i++) { final ClassicHttpRequest request = ClassicRequestBuilder.get() .setHttpHost(target) .setPath(requestUri) .build(); request.setEntity(requestBodies[i]); try (ClassicHttpResponse response = httpRequester.execute(target, request, Timeout.ofSeconds(5), context)) { System.out.println(requestUri + "->" + response.getCode()); System.out.println(EntityUtils.toString(response.getEntity())); System.out.println("=============="); } } } } httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicReverseProxyExample.java0100644 0000000 0000000 00000026333 00000250600 030550 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.examples; import java.io.IOException; import java.net.SocketException; import java.net.SocketTimeoutException; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.ExceptionListener; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.bootstrap.HttpRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpServer; import org.apache.hc.core5.http.impl.bootstrap.RequesterBootstrap; import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.pool.ConnPoolListener; import org.apache.hc.core5.pool.ConnPoolStats; import org.apache.hc.core5.pool.PoolStats; import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; /** * Example of embedded HTTP/1.1 reverse proxy using classic I/O. */ public class ClassicReverseProxyExample { public static void main(final String[] args) throws Exception { if (args.length < 1) { System.out.println("Usage: [listener port]"); System.exit(1); } final HttpHost targetHost = HttpHost.create(args[0]); int port = 8080; if (args.length > 1) { port = Integer.parseInt(args[1]); } System.out.println("Reverse proxy to " + targetHost); final HttpRequester requester = RequesterBootstrap.bootstrap() .setStreamListener(new Http1StreamListener() { @Override public void onRequestHead(final HttpConnection connection, final HttpRequest request) { System.out.println("[proxy->origin] " + Thread.currentThread() + " " + request.getMethod() + " " + request.getRequestUri()); } @Override public void onResponseHead(final HttpConnection connection, final HttpResponse response) { System.out.println("[proxy<-origin] " + Thread.currentThread() + " status " + response.getCode()); } @Override public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) { System.out.println("[proxy<-origin] " + Thread.currentThread() + " exchange completed; " + "connection " + (keepAlive ? "kept alive" : "cannot be kept alive")); } }) .setConnPoolListener(new ConnPoolListener() { @Override public void onLease(final HttpHost route, final ConnPoolStats connPoolStats) { final StringBuilder buf = new StringBuilder(); buf.append("[proxy->origin] ").append(Thread.currentThread()).append(" connection leased ").append(route); System.out.println(buf); } @Override public void onRelease(final HttpHost route, final ConnPoolStats connPoolStats) { final StringBuilder buf = new StringBuilder(); buf.append("[proxy->origin] ").append(Thread.currentThread()).append(" connection released ").append(route); final PoolStats totals = connPoolStats.getTotalStats(); buf.append("; total kept alive: ").append(totals.getAvailable()).append("; "); buf.append("total allocated: ").append(totals.getLeased() + totals.getAvailable()); buf.append(" of ").append(totals.getMax()); System.out.println(buf); } }) .create(); final HttpServer server = ServerBootstrap.bootstrap() .setListenerPort(port) .setStreamListener(new Http1StreamListener() { @Override public void onRequestHead(final HttpConnection connection, final HttpRequest request) { System.out.println("[client->proxy] " + Thread.currentThread() + " " + request.getMethod() + " " + request.getRequestUri()); } @Override public void onResponseHead(final HttpConnection connection, final HttpResponse response) { System.out.println("[client<-proxy] " + Thread.currentThread() + " status " + response.getCode()); } @Override public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) { System.out.println("[client<-proxy] " + Thread.currentThread() + " exchange completed; " + "connection " + (keepAlive ? "kept alive" : "cannot be kept alive")); } }) .setExceptionListener(new ExceptionListener() { @Override public void onError(final Exception ex) { if (ex instanceof SocketException) { System.out.println("[client->proxy] " + Thread.currentThread() + " " + ex.getMessage()); } else { System.out.println("[client->proxy] " + Thread.currentThread() + " " + ex.getMessage()); ex.printStackTrace(System.out); } } @Override public void onError(final HttpConnection connection, final Exception ex) { if (ex instanceof SocketTimeoutException) { System.out.println("[client->proxy] " + Thread.currentThread() + " time out"); } else if (ex instanceof SocketException || ex instanceof ConnectionClosedException) { System.out.println("[client->proxy] " + Thread.currentThread() + " " + ex.getMessage()); } else { System.out.println("[client->proxy] " + Thread.currentThread() + " " + ex.getMessage()); ex.printStackTrace(System.out); } } }) .register("*", new ProxyHandler(targetHost, requester)) .create(); server.start(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { server.close(CloseMode.GRACEFUL); requester.close(CloseMode.GRACEFUL); })); System.out.println("Listening on port " + port); server.awaitTermination(TimeValue.MAX_VALUE); } private final static Set HOP_BY_HOP = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( TextUtils.toLowerCase(HttpHeaders.HOST), TextUtils.toLowerCase(HttpHeaders.CONTENT_LENGTH), TextUtils.toLowerCase(HttpHeaders.TRANSFER_ENCODING), TextUtils.toLowerCase(HttpHeaders.CONNECTION), TextUtils.toLowerCase(HttpHeaders.KEEP_ALIVE), TextUtils.toLowerCase(HttpHeaders.PROXY_AUTHENTICATE), TextUtils.toLowerCase(HttpHeaders.TE), TextUtils.toLowerCase(HttpHeaders.TRAILER), TextUtils.toLowerCase(HttpHeaders.UPGRADE)))); static class ProxyHandler implements HttpRequestHandler { private final HttpHost targetHost; private final HttpRequester requester; public ProxyHandler( final HttpHost targetHost, final HttpRequester requester) { super(); this.targetHost = targetHost; this.requester = requester; } @Override public void handle( final ClassicHttpRequest incomingRequest, final ClassicHttpResponse outgoingResponse, final HttpContext serverContext) throws HttpException, IOException { final HttpCoreContext clientContext = HttpCoreContext.create(); final ClassicHttpRequest outgoingRequest = new BasicClassicHttpRequest( incomingRequest.getMethod(), targetHost, incomingRequest.getPath()); for (final Iterator
it = incomingRequest.headerIterator(); it.hasNext(); ) { final Header header = it.next(); if (!HOP_BY_HOP.contains(TextUtils.toLowerCase(header.getName()))) { outgoingRequest.addHeader(header); } } outgoingRequest.setEntity(incomingRequest.getEntity()); final ClassicHttpResponse incomingResponse = requester.execute( targetHost, outgoingRequest, Timeout.ofMinutes(1), clientContext); outgoingResponse.setCode(incomingResponse.getCode()); for (final Iterator
it = incomingResponse.headerIterator(); it.hasNext(); ) { final Header header = it.next(); if (!HOP_BY_HOP.contains(TextUtils.toLowerCase(header.getName()))) { outgoingResponse.addHeader(header); } } outgoingResponse.setEntity(incomingResponse.getEntity()); } } } httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicServerFilterExample.java0100644 0000000 0000000 00000013565 00000250600 030512 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.examples; import java.io.IOException; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.impl.bootstrap.HttpServer; import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap; import org.apache.hc.core5.http.impl.bootstrap.StandardFilter; import org.apache.hc.core5.http.io.HttpFilterChain; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.io.support.AbstractHttpServerAuthFilter; import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.util.TimeValue; /** * Example of using classic I/O request filters with an embedded HTTP/1.1 server. */ public class ClassicServerFilterExample { public static void main(final String[] args) throws Exception { int port = 8080; if (args.length >= 1) { port = Integer.parseInt(args[0]); } final SocketConfig socketConfig = SocketConfig.custom() .setSoTimeout(15, TimeUnit.SECONDS) .setTcpNoDelay(true) .build(); final HttpServer server = ServerBootstrap.bootstrap() .setListenerPort(port) .setSocketConfig(socketConfig) // Replace standard expect-continue handling with a custom auth filter .replaceFilter(StandardFilter.EXPECT_CONTINUE.name(), new AbstractHttpServerAuthFilter(false) { @Override protected String parseChallengeResponse( final String authorizationValue, final HttpContext context) throws HttpException { return authorizationValue; } @Override protected boolean authenticate( final String challengeResponse, final URIAuthority authority, final String requestUri, final HttpContext context) { return "let me pass".equals(challengeResponse); } @Override protected String generateChallenge( final String challengeResponse, final URIAuthority authority, final String requestUri, final HttpContext context) { return "who goes there?"; } }) // Add a custom request filter at the beginning of the processing pipeline .addFilterFirst("my-filter", (request, responseTrigger, context, chain) -> { if (request.getRequestUri().equals("/back-door")) { final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK); response.setEntity(new StringEntity("Welcome", ContentType.TEXT_PLAIN)); responseTrigger.submitResponse(response); } else { chain.proceed(request, new HttpFilterChain.ResponseTrigger() { @Override public void sendInformation(final ClassicHttpResponse response) throws HttpException, IOException { responseTrigger.sendInformation(response); } @Override public void submitResponse(final ClassicHttpResponse response) throws HttpException, IOException { response.addHeader("X-Filter", "My-Filter"); responseTrigger.submitResponse(response); } }, context); } }) // Application request handler .register("*", (request, response, context) -> { // do something useful response.setCode(HttpStatus.SC_OK); response.setEntity(new StringEntity("Hello")); }) .create(); server.start(); Runtime.getRuntime().addShutdownHook(new Thread(() -> server.close(CloseMode.GRACEFUL))); System.out.println("Listening on port " + port); server.awaitTermination(TimeValue.MAX_VALUE); } } httpcore5/src/test/java/org/apache/hc/core5/http/examples/PrintVersionInfo.java0100644 0000000 0000000 00000005461 00000250600 026532 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.examples; import org.apache.hc.core5.util.VersionInfo; /** * Prints version information for debugging purposes. * This can be used to verify that the correct versions of the * HttpComponent JARs are picked up from the classpath. * * */ public class PrintVersionInfo { /** A default list of module packages. */ private final static String[] MODULE_LIST = { "org.apache.http", // HttpCore "org.apache.http.client", // HttpClient }; /** * Prints version information. * * @param args command line arguments. Leave empty to print version * information for the default packages. Otherwise, pass * a list of packages for which to get version info. */ public static void main(final String args[]) { final String[] pckgs = (args.length > 0) ? args : MODULE_LIST; VersionInfo[] via = VersionInfo.loadVersionInfo(pckgs, null); System.out.println("version info for thread context classloader:"); for (int i = 0; i < via.length; i++) System.out.println(via[i]); System.out.println(); // if the version information for the classloader of this class // is different from that for the thread context classloader, // there may be a problem with multiple versions in the classpath via = VersionInfo.loadVersionInfo (pckgs, PrintVersionInfo.class.getClassLoader()); System.out.println("version info for static classloader:"); for (int i = 0; i < via.length; i++) System.out.println(via[i]); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/0040755 0000000 0000000 00000000000 00000250600 021531 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/impl/IncomingEntityDetailsTest.java0100644 0000000 0000000 00000007601 00000250600 027503 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl; import static org.junit.jupiter.api.Assertions.assertAll; 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; import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.MessageHeaders; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.HeaderGroup; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class IncomingEntityDetailsTest { @Test void getContentLengthEmpty() { final MessageHeaders messageHeaders = new HeaderGroup(); final IncomingEntityDetails incomingEntityDetails = new IncomingEntityDetails(messageHeaders); assertAll( () -> assertEquals(-1, incomingEntityDetails.getContentLength()), () -> assertNull(incomingEntityDetails.getContentType()), () -> assertNull(incomingEntityDetails.getContentEncoding()), () -> assertEquals(0, incomingEntityDetails.getTrailerNames().size()) ); } @Test void messageHeadersNull() { Assertions.assertThrows(NullPointerException.class, () -> new IncomingEntityDetails(null), "Message Header Null"); } @Test void getContentLength() { final MessageHeaders messageHeaders = new HeaderGroup(); final HeaderGroup headerGroup = new HeaderGroup(); final Header header = new BasicHeader("name", "value"); headerGroup.addHeader(header); final IncomingEntityDetails incomingEntityDetails = new IncomingEntityDetails(messageHeaders); assertAll( () -> assertEquals(-1, incomingEntityDetails.getContentLength()), () -> assertTrue(incomingEntityDetails.isChunked()) ); } @Test void getTrailerNames() { final HeaderGroup messageHeaders = new HeaderGroup(); final Header header = new BasicHeader(HttpHeaders.TRAILER, "a, b, c, c"); messageHeaders.setHeaders(header); final IncomingEntityDetails incomingEntityDetails = new IncomingEntityDetails(messageHeaders); final Set incomingSet = incomingEntityDetails.getTrailerNames(); assertAll( () -> assertFalse(incomingSet.isEmpty()), () -> assertTrue(incomingSet.containsAll(Stream.of("a", "b", "c") .collect(Collectors.toCollection(HashSet::new)))) ); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/TestDefaultConnectionReuseStrategy.java0100644 0000000 0000000 00000030577 00000250600 031400 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class TestDefaultConnectionReuseStrategy { /** HTTP context. */ private HttpCoreContext context; /** The reuse strategy to be tested. */ private ConnectionReuseStrategy reuseStrategy; @BeforeEach void setUp() { reuseStrategy = DefaultConnectionReuseStrategy.INSTANCE; context = HttpCoreContext.create(); } @Test void testInvalidResponseArg() { Assertions.assertThrows(NullPointerException.class, () -> reuseStrategy.keepAlive(null, null, this.context)); } @Test void testNoContentLengthResponseHttp1_0() { context.setProtocolVersion(HttpVersion.HTTP_1_0); final HttpResponse response = new BasicHttpResponse(200, "OK"); Assertions.assertFalse(reuseStrategy.keepAlive(null, response, context)); } @Test void testNoContentLengthResponseHttp1_1() { final HttpResponse response = new BasicHttpResponse(200, "OK"); Assertions.assertFalse(reuseStrategy.keepAlive(null, response, context)); } @Test void testChunkedContent() { final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Transfer-Encoding", "chunked"); Assertions.assertTrue(reuseStrategy.keepAlive(null, response, context)); } @Test void testIgnoreInvalidKeepAlive() { context.setProtocolVersion(HttpVersion.HTTP_1_0); final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Connection", "keep-alive"); Assertions.assertFalse(reuseStrategy.keepAlive(null, response, context)); } @Test void testExplicitClose() { // Use HTTP 1.1 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Transfer-Encoding", "chunked"); response.addHeader("Connection", "close"); Assertions.assertFalse(reuseStrategy.keepAlive(null, response, context)); } @Test void testExplicitKeepAlive() { // Use HTTP 1.0 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.setVersion(HttpVersion.HTTP_1_0); response.addHeader("Content-Length", "10"); response.addHeader("Connection", "keep-alive"); Assertions.assertTrue(reuseStrategy.keepAlive(null, response, context)); } @Test void testHTTP10Default() { final HttpResponse response = new BasicHttpResponse(200, "OK"); response.setVersion(HttpVersion.HTTP_1_0); response.addHeader("Content-Length", "10"); Assertions.assertFalse(reuseStrategy.keepAlive(null, response, context)); } @Test void testHTTP11Default() { final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Content-Length", "10"); Assertions.assertTrue(reuseStrategy.keepAlive(null, response, context)); } @Test void testBrokenConnectionDirective1() { // Use HTTP 1.0 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.setVersion(HttpVersion.HTTP_1_0); response.addHeader("Content-Length", "10"); response.addHeader("Connection", "keep--alive"); Assertions.assertFalse(reuseStrategy.keepAlive(null, response, context)); } @Test void testBrokenConnectionDirective2() { // Use HTTP 1.0 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.setVersion(HttpVersion.HTTP_1_0); response.addHeader("Content-Length", "10"); response.addHeader("Connection", null); Assertions.assertFalse(reuseStrategy.keepAlive(null, response, context)); } @Test void testConnectionTokens1() { // Use HTTP 1.1 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Transfer-Encoding", "chunked"); response.addHeader("Connection", "yadda, cLOSe, dumdy"); Assertions.assertFalse(reuseStrategy.keepAlive(null, response, context)); } @Test void testConnectionTokens2() { // Use HTTP 1.1 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Transfer-Encoding", "chunked"); response.addHeader("Connection", "yadda, kEEP-alive, dumdy"); Assertions.assertTrue(reuseStrategy.keepAlive(null, response, context)); } @Test void testConnectionTokens3() { // Use HTTP 1.1 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Transfer-Encoding", "chunked"); response.addHeader("Connection", "yadda, keep-alive, close, dumdy"); Assertions.assertFalse(reuseStrategy.keepAlive(null, response, context)); } @Test void testConnectionTokens4() { // Use HTTP 1.1 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Transfer-Encoding", "chunked"); response.addHeader("Connection", "yadda, close, dumdy"); response.addHeader("Proxy-Connection", "keep-alive"); // Connection takes precedence over Proxy-Connection Assertions.assertFalse(reuseStrategy.keepAlive(null, response, context)); } @Test void testConnectionTokens5() { // Use HTTP 1.1 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Transfer-Encoding", "chunked"); response.addHeader("Connection", "yadda, dumdy"); response.addHeader("Proxy-Connection", "close"); // Connection takes precedence over Proxy-Connection, // even if it doesn't contain a recognized token. // Default for HTTP/1.1 is to keep alive. Assertions.assertTrue(reuseStrategy.keepAlive(null, response, context)); } @Test void testConnectionTokens6() { // Use HTTP 1.1 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Transfer-Encoding", "chunked"); response.addHeader("Connection", ""); response.addHeader("Proxy-Connection", "close"); // Connection takes precedence over Proxy-Connection, // even if it is empty. Default for HTTP/1.1 is to keep alive. Assertions.assertTrue(reuseStrategy.keepAlive(null, response, context)); } @Test void testMultipleContentLength() { // Use HTTP 1.1 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Content-Length", "10"); response.addHeader("Content-Length", "11"); Assertions.assertFalse(reuseStrategy.keepAlive(null, response, context)); } @Test void testNoContentResponse() { // Use HTTP 1.1 final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content"); Assertions.assertTrue(reuseStrategy.keepAlive(null, response, context)); } @Test void testNoContentResponseHttp10() { // Use HTTP 1.0 final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content"); response.setVersion(HttpVersion.HTTP_1_0); Assertions.assertFalse(reuseStrategy.keepAlive(null, response, context)); } @Test void testRequestExplicitClose() { final HttpRequest request = new BasicHttpRequest(Method.GET, "/"); request.addHeader("Connection", "close"); final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Transfer-Encoding", "chunked"); response.addHeader("Connection", "keep-alive"); Assertions.assertFalse(reuseStrategy.keepAlive(request, response, context)); } @Test void testRequestNoExplicitClose() { final HttpRequest request = new BasicHttpRequest(Method.GET, "/"); request.addHeader("Connection", "blah, blah, blah"); final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Transfer-Encoding", "chunked"); response.addHeader("Connection", "keep-alive"); Assertions.assertTrue(reuseStrategy.keepAlive(request, response, context)); } @Test void testRequestExplicitCloseMultipleTokens() { final HttpRequest request = new BasicHttpRequest(Method.GET, "/"); request.addHeader("Connection", "blah, blah, blah"); request.addHeader("Connection", "keep-alive"); request.addHeader("Connection", "close"); final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Transfer-Encoding", "chunked"); response.addHeader("Connection", "keep-alive"); Assertions.assertFalse(reuseStrategy.keepAlive(request, response, context)); } @Test void testRequestClose() { final HttpRequest request = new BasicHttpRequest(Method.GET, "/"); request.addHeader("Connection", "close"); final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Content-Length", "10"); response.addHeader("Connection", "keep-alive"); Assertions.assertFalse(reuseStrategy.keepAlive(request, response, context)); } @Test void testHeadRequestWithout() { final HttpRequest request = new BasicHttpRequest(Method.HEAD, "/"); final HttpResponse response = new BasicHttpResponse(200, "OK"); Assertions.assertTrue(reuseStrategy.keepAlive(request, response, context)); } @Test void testHttp204ContentLengthGreaterThanZero() { final HttpResponse response = new BasicHttpResponse(204, "OK"); response.addHeader("Content-Length", "10"); response.addHeader("Connection", "keep-alive"); Assertions.assertFalse(reuseStrategy.keepAlive(null, response, context)); } @Test void testHttp204ContentLengthEqualToZero() { final HttpResponse response = new BasicHttpResponse(204, "OK"); response.addHeader("Content-Length", "0"); response.addHeader("Connection", "keep-alive"); Assertions.assertTrue(reuseStrategy.keepAlive(null, response, context)); } @Test void testHttp204ChunkedContent() { final HttpResponse response = new BasicHttpResponse(204, "OK"); response.addHeader("Transfer-Encoding", "chunked"); response.addHeader("Connection", "keep-alive"); Assertions.assertFalse(reuseStrategy.keepAlive(null, response, context)); } @Test void testResponseHTTP10TransferEncodingPresent() { final HttpResponse response = new BasicHttpResponse(200, "OK"); response.setVersion(HttpVersion.HTTP_1_0); response.addHeader("Transfer-Encoding", "chunked"); response.addHeader("Connection", "keep-alive"); Assertions.assertFalse(reuseStrategy.keepAlive(null, response, context)); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/TestDefaultContentLengthStrategy.java0100644 0000000 0000000 00000014727 00000250600 031050 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.HttpMessage; import org.apache.hc.core5.http.NotImplementedException; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.HeaderGroup; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestDefaultContentLengthStrategy { static class TestHttpMessage extends HeaderGroup implements HttpMessage { private static final long serialVersionUID = 1L; @Override public ProtocolVersion getVersion() { return null; } @Override public void addHeader(final String name, final Object value) { addHeader(new BasicHeader(name, value)); } @Override public void setHeader(final String name, final Object value) { setHeader(new BasicHeader(name, value)); } @Override public void setVersion(final ProtocolVersion version) { } } @Test void testEntityWithChunkTransferEncoding() throws Exception { final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy(); final HttpMessage message = new TestHttpMessage(); message.addHeader("Transfer-Encoding", "Chunked"); Assertions.assertEquals(ContentLengthStrategy.CHUNKED, lenStrategy.determineLength(message)); } @Test void testEntityWithChunkTransferEncodingAndEmptyTokens() throws Exception { final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy(); final HttpMessage message = new TestHttpMessage(); message.addHeader("Transfer-Encoding", ",,Chunked,,"); Assertions.assertEquals(ContentLengthStrategy.CHUNKED, lenStrategy.determineLength(message)); } @Test void testEntityWithChunkTransferEncodingDoubleChunk() { final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy(); final HttpMessage message = new TestHttpMessage(); message.addHeader("Transfer-Encoding", "Chunked,Chunked"); Assertions.assertThrows(NotImplementedException.class, () -> lenStrategy.determineLength(message)); } @Test void testEntityWithChunkTransferEncodingUnknown1() { final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy(); final HttpMessage message = new TestHttpMessage(); message.addHeader("Transfer-Encoding", "blah"); Assertions.assertThrows(NotImplementedException.class, () -> lenStrategy.determineLength(message)); } @Test void testEntityWithChunkTransferEncodingUnknown2() { final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy(); final HttpMessage message = new TestHttpMessage(); message.addHeader("Transfer-Encoding", "blah, chunked"); Assertions.assertThrows(NotImplementedException.class, () -> lenStrategy.determineLength(message)); } @Test void testEntityWithChunkTransferEncodingUnknown3() { final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy(); final HttpMessage message = new TestHttpMessage(); message.addHeader("Transfer-Encoding", "chunked,blah"); Assertions.assertThrows(NotImplementedException.class, () -> lenStrategy.determineLength(message)); } @Test void testEntityWithIdentityTransferEncoding() { final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy(); final HttpMessage message = new TestHttpMessage(); message.addHeader("Transfer-Encoding", "Identity"); Assertions.assertThrows(NotImplementedException.class, () -> lenStrategy.determineLength(message)); } @Test void testEntityWithContentLength() throws Exception { final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy(); final HttpMessage message = new TestHttpMessage(); message.addHeader("Content-Length", "100"); Assertions.assertEquals(100, lenStrategy.determineLength(message)); } @Test void testEntityWithInvalidContentLength() { final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy(); final HttpMessage message = new TestHttpMessage(); message.addHeader("Content-Length", "whatever"); Assertions.assertThrows(ProtocolException.class, () -> lenStrategy.determineLength(message)); } @Test void testEntityWithNegativeContentLength() { final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy(); final HttpMessage message = new TestHttpMessage(); message.addHeader("Content-Length", "-10"); Assertions.assertThrows(ProtocolException.class, () -> lenStrategy.determineLength(message)); } @Test void testEntityNoContentDelimiter() throws Exception { final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy(); final HttpMessage message = new TestHttpMessage(); Assertions.assertEquals(ContentLengthStrategy.UNDEFINED, lenStrategy.determineLength(message)); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/TestEnglishReasonPhraseCatalog.java0100644 0000000 0000000 00000005702 00000250600 030434 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import org.apache.hc.core5.http.HttpStatus; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * * Unit tests for {@link EnglishReasonPhraseCatalog} * */ class TestEnglishReasonPhraseCatalog { @Test void testReasonPhrases() throws IllegalAccessException { final Field[] publicFields = HttpStatus.class.getFields(); Assertions.assertNotNull( publicFields ); Assertions.assertTrue( publicFields.length > 0 ); for (final Field f : publicFields) { final int modifiers = f.getModifiers(); if (f.getType() == int.class && Modifier.isPublic(modifiers) && Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers)) { final int iValue = f.getInt(null); final String text = EnglishReasonPhraseCatalog. INSTANCE.getReason(iValue, null); Assertions.assertNotNull("text is null for HttpStatus." + f.getName(), text); Assertions.assertTrue(text.length() > 0); } } } @Test void testStatusInvalid() { Assertions.assertThrows(IllegalArgumentException.class, () -> EnglishReasonPhraseCatalog.INSTANCE.getReason(-1, null)); Assertions.assertThrows(IllegalArgumentException.class, () -> EnglishReasonPhraseCatalog.INSTANCE.getReason(99, null)); Assertions.assertThrows(IllegalArgumentException.class, () -> EnglishReasonPhraseCatalog.INSTANCE.getReason(600, null)); } @Test void testStatusAll() { for (int i = 100; i < 600; i++) { EnglishReasonPhraseCatalog.INSTANCE.getReason(i, null); } } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/0040755 0000000 0000000 00000000000 00000250600 022140 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestBHttpConnectionBase.java0100644 0000000 0000000 00000030711 00000250600 027476 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; class TestBHttpConnectionBase { @Mock private Socket socket; private BHttpConnectionBase conn; @BeforeEach void prepareMocks() { MockitoAnnotations.openMocks(this); conn = new BHttpConnectionBase(Http1Config.DEFAULT, null, null); } @Test void testBasics() { Assertions.assertFalse(conn.isOpen()); Assertions.assertNull(conn.getLocalAddress()); Assertions.assertNull(conn.getRemoteAddress()); Assertions.assertEquals("[Not bound]", conn.toString()); } @Test void testSocketBind() throws Exception { final InetAddress localAddress = InetAddress.getByAddress(new byte[] {127, 0, 0, 1}); final int localPort = 8888; final InetAddress remoteAddress = InetAddress.getByAddress(new byte[] {10, 0, 0, 2}); final int remotePort = 80; final InetSocketAddress localSockAddress = new InetSocketAddress(localAddress, localPort); final InetSocketAddress remoteSockAddress = new InetSocketAddress(remoteAddress, remotePort); Mockito.when(socket.getLocalSocketAddress()).thenReturn(localSockAddress); Mockito.when(socket.getRemoteSocketAddress()).thenReturn(remoteSockAddress); conn.bind(socket); Assertions.assertEquals("127.0.0.1:8888<->10.0.0.2:80", conn.toString()); Assertions.assertTrue(conn.isOpen()); Assertions.assertEquals(new InetSocketAddress( InetAddress.getByAddress(new byte[] {127, 0, 0, 1}), 8888), conn.getLocalAddress()); Assertions.assertEquals(new InetSocketAddress( InetAddress.getByAddress(new byte[] {10, 0, 0, 2}), 80), conn.getRemoteAddress()); } @Test void testConnectionClose() throws Exception { final OutputStream outStream = Mockito.mock(OutputStream.class); Mockito.when(socket.getOutputStream()).thenReturn(outStream); conn.bind(socket); conn.ensureOpen(); conn.outbuffer.write(0, outStream); Assertions.assertTrue(conn.isOpen()); conn.close(); Assertions.assertFalse(conn.isOpen()); Mockito.verify(outStream, Mockito.times(1)).write( ArgumentMatchers.any(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt()); Mockito.verify(socket, Mockito.times(1)).close(); conn.close(); Mockito.verify(socket, Mockito.times(1)).close(); Mockito.verify(outStream, Mockito.times(1)).write( ArgumentMatchers.any(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt()); } @Test void testConnectionShutdown() throws Exception { final OutputStream outStream = Mockito.mock(OutputStream.class); conn.bind(socket); conn.ensureOpen(); conn.outbuffer.write(0, outStream); Assertions.assertTrue(conn.isOpen()); conn.close(CloseMode.GRACEFUL); Assertions.assertFalse(conn.isOpen()); Mockito.verify(outStream, Mockito.never()).write( ArgumentMatchers.any(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt()); Mockito.verify(socket, Mockito.never()).shutdownInput(); Mockito.verify(socket, Mockito.never()).shutdownOutput(); Mockito.verify(socket, Mockito.times(1)).close(); conn.close(); Mockito.verify(socket, Mockito.times(1)).close(); conn.close(CloseMode.GRACEFUL); Mockito.verify(socket, Mockito.times(1)).close(); } @Test void testCreateEntityLengthDelimited() throws Exception { final InputStream inStream = Mockito.mock(InputStream.class); final ClassicHttpResponse message = new BasicClassicHttpResponse(200, "OK"); message.addHeader("Content-Length", "10"); message.addHeader("Content-Type", "stuff"); message.addHeader("Content-Encoding", "chunked"); final HttpEntity entity = conn.createIncomingEntity(message, conn.inBuffer, inStream, 10); Assertions.assertNotNull(entity); Assertions.assertFalse(entity.isChunked()); Assertions.assertEquals(10, entity.getContentLength()); Assertions.assertEquals("stuff", entity.getContentType()); Assertions.assertEquals("chunked", entity.getContentEncoding()); final InputStream content = entity.getContent(); Assertions.assertNotNull(content); Assertions.assertInstanceOf(ContentLengthInputStream.class, content); } @Test void testCreateEntityInputChunked() throws Exception { final InputStream inStream = Mockito.mock(InputStream.class); final ClassicHttpResponse message = new BasicClassicHttpResponse(200, "OK"); final HttpEntity entity = conn.createIncomingEntity(message, conn.inBuffer, inStream, ContentLengthStrategy.CHUNKED); Assertions.assertNotNull(entity); Assertions.assertTrue(entity.isChunked()); Assertions.assertEquals(-1, entity.getContentLength()); final InputStream content = entity.getContent(); Assertions.assertNotNull(content); Assertions.assertInstanceOf(ChunkedInputStream.class, content); } @Test void testCreateEntityInputUndefined() throws Exception { final InputStream inStream = Mockito.mock(InputStream.class); final ClassicHttpResponse message = new BasicClassicHttpResponse(200, "OK"); final HttpEntity entity = conn.createIncomingEntity(message, conn.inBuffer, inStream, ContentLengthStrategy.UNDEFINED); Assertions.assertNotNull(entity); Assertions.assertFalse(entity.isChunked()); Assertions.assertEquals(-1, entity.getContentLength()); final InputStream content = entity.getContent(); Assertions.assertNotNull(content); Assertions.assertInstanceOf(IdentityInputStream.class, content); } @Test void testSetSocketTimeout() throws Exception { conn.bind(socket); conn.setSocketTimeout(Timeout.ofMilliseconds(123)); Mockito.verify(socket, Mockito.times(1)).setSoTimeout(123); } @Test void testSetSocketTimeoutException() throws Exception { conn.bind(socket); Mockito.doThrow(new SocketException()).when(socket).setSoTimeout(ArgumentMatchers.anyInt()); conn.setSocketTimeout(Timeout.ofMilliseconds(123)); Mockito.verify(socket, Mockito.times(1)).setSoTimeout(123); } @Test void testGetSocketTimeout() throws Exception { Assertions.assertEquals(Timeout.INFINITE, conn.getSocketTimeout()); Mockito.when(socket.getSoTimeout()).thenReturn(345); conn.bind(socket); Assertions.assertEquals(Timeout.ofMilliseconds(345), conn.getSocketTimeout()); } @Test void testGetSocketTimeoutException() throws Exception { Assertions.assertEquals(Timeout.INFINITE, conn.getSocketTimeout()); Mockito.when(socket.getSoTimeout()).thenThrow(new SocketException()); conn.bind(socket); Assertions.assertEquals(Timeout.INFINITE, conn.getSocketTimeout()); } @Test void testAwaitInputInBuffer() throws Exception { final ByteArrayInputStream inStream = new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5}); conn.bind(socket); conn.ensureOpen(); conn.inBuffer.read(inStream); Assertions.assertTrue(conn.awaitInput(Timeout.ofMilliseconds(432))); Mockito.verify(socket, Mockito.never()).setSoTimeout(ArgumentMatchers.anyInt()); } @Test void testAwaitInputInSocket() throws Exception { final ByteArrayInputStream inStream = new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5}); Mockito.when(socket.getInputStream()).thenReturn(inStream); Mockito.when(socket.getSoTimeout()).thenReturn(345); conn.bind(socket); conn.ensureOpen(); Assertions.assertTrue(conn.awaitInput(Timeout.ofMilliseconds(432))); Mockito.verify(socket, Mockito.times(1)).setSoTimeout(432); Mockito.verify(socket, Mockito.times(1)).setSoTimeout(345); } @Test void testAwaitInputNoData() throws Exception { final InputStream inStream = Mockito.mock(InputStream.class); Mockito.when(socket.getInputStream()).thenReturn(inStream); Mockito.when(inStream.read(ArgumentMatchers.any(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())) .thenReturn(-1); conn.bind(socket); conn.ensureOpen(); Assertions.assertFalse(conn.awaitInput(Timeout.ofMilliseconds(432))); } @Test void testStaleWhenClosed() throws Exception { final OutputStream outStream = Mockito.mock(OutputStream.class); Mockito.when(socket.getOutputStream()).thenReturn(outStream); conn.bind(socket); conn.ensureOpen(); conn.close(); Assertions.assertTrue(conn.isStale()); } @Test void testNotStaleWhenHasData() throws Exception { final ByteArrayInputStream inStream = new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5}); Mockito.when(socket.getInputStream()).thenReturn(inStream); conn.bind(socket); conn.ensureOpen(); Assertions.assertFalse(conn.isStale()); } @Test void testStaleWhenEndOfStream() throws Exception { final InputStream inStream = Mockito.mock(InputStream.class); Mockito.when(socket.getInputStream()).thenReturn(inStream); Mockito.when(inStream.read(ArgumentMatchers.any(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())) .thenReturn(-1); conn.bind(socket); conn.ensureOpen(); Assertions.assertTrue(conn.isStale()); } @Test void testNotStaleWhenTimeout() throws Exception { final InputStream inStream = Mockito.mock(InputStream.class); Mockito.when(socket.getInputStream()).thenReturn(inStream); Mockito.when(inStream.read(ArgumentMatchers.any(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())) .thenThrow(new SocketTimeoutException()); conn.bind(socket); conn.ensureOpen(); Assertions.assertFalse(conn.isStale()); } @Test void testStaleWhenIOError() throws Exception { final InputStream inStream = Mockito.mock(InputStream.class); Mockito.when(socket.getInputStream()).thenReturn(inStream); Mockito.when(inStream.read(ArgumentMatchers.any(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())) .thenThrow(new SocketException()); conn.bind(socket); conn.ensureOpen(); Assertions.assertTrue(conn.isStale()); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestChunkCoding.java0100644 0000000 0000000 00000051010 00000250600 026031 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InterruptedIOException; import java.nio.charset.StandardCharsets; import java.util.Arrays; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.MalformedChunkCodingException; import org.apache.hc.core5.http.MessageConstraintException; import org.apache.hc.core5.http.StreamClosedException; import org.apache.hc.core5.http.io.SessionInputBuffer; import org.apache.hc.core5.http.io.SessionOutputBuffer; import org.apache.hc.core5.http.message.BasicHeader; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestChunkCoding { private final static String CHUNKED_INPUT = "10;key=\"value\"\r\n1234567890123456\r\n5\r\n12345\r\n0\r\nFooter1: abcde\r\nFooter2: fghij\r\n"; private final static String CHUNKED_RESULT = "123456789012345612345"; @Test void testChunkedInputStreamLargeBuffer() throws IOException { final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(CHUNKED_INPUT.getBytes(StandardCharsets.US_ASCII)); final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream); final byte[] buffer = new byte[300]; final ByteArrayOutputStream out = new ByteArrayOutputStream(); int len; while ((len = in.read(buffer)) > 0) { out.write(buffer, 0, len); } Assertions.assertEquals(-1, in.read(buffer)); Assertions.assertEquals(-1, in.read(buffer)); in.close(); final String result = new String(out.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals(CHUNKED_RESULT, result); final Header[] footers = in.getFooters(); Assertions.assertNotNull(footers); Assertions.assertEquals(2, footers.length); Assertions.assertEquals("Footer1", footers[0].getName()); Assertions.assertEquals("abcde", footers[0].getValue()); Assertions.assertEquals("Footer2", footers[1].getName()); Assertions.assertEquals("fghij", footers[1].getValue()); } //Test for when buffer is smaller than chunk size. @Test void testChunkedInputStreamSmallBuffer() throws IOException { final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(CHUNKED_INPUT.getBytes(StandardCharsets.US_ASCII)); final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream); final byte[] buffer = new byte[7]; final ByteArrayOutputStream out = new ByteArrayOutputStream(); int len; while ((len = in.read(buffer)) > 0) { out.write(buffer, 0, len); } Assertions.assertEquals(-1, in.read(buffer)); Assertions.assertEquals(-1, in.read(buffer)); in.close(); final Header[] footers = in.getFooters(); Assertions.assertNotNull(footers); Assertions.assertEquals(2, footers.length); Assertions.assertEquals("Footer1", footers[0].getName()); Assertions.assertEquals("abcde", footers[0].getValue()); Assertions.assertEquals("Footer2", footers[1].getName()); Assertions.assertEquals("fghij", footers[1].getValue()); } // One byte read @Test void testChunkedInputStreamOneByteRead() throws IOException { final String s = "5\r\n01234\r\n5\r\n56789\r\n0\r\n"; final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); try (ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) { int ch; int i = '0'; while ((ch = in.read()) != -1) { Assertions.assertEquals(i, ch); i++; } Assertions.assertEquals(-1, in.read()); Assertions.assertEquals(-1, in.read()); } } @Test void testAvailable() throws IOException { final String s = "5\r\n12345\r\n0\r\n"; final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); try (ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) { Assertions.assertEquals(0, in.available()); in.read(); Assertions.assertEquals(4, in.available()); } } @Test void testChunkedInputStreamClose() throws IOException { final String s = "5\r\n01234\r\n5\r\n56789\r\n0\r\n"; final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream); in.close(); in.close(); Assertions.assertThrows(StreamClosedException.class, () -> in.read()); final byte[] tmp = new byte[10]; Assertions.assertThrows(StreamClosedException.class, () -> in.read(tmp)); Assertions.assertThrows(StreamClosedException.class, () -> in.read(tmp, 0, tmp.length)); } // Missing closing chunk @Test void testChunkedInputStreamNoClosingChunk() throws IOException { final String s = "5\r\n01234\r\n"; final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); try (ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) { final byte[] tmp = new byte[5]; Assertions.assertEquals(5, in.read(tmp)); Assertions.assertThrows(ConnectionClosedException.class, () -> in.read()); Assertions.assertThrows(ConnectionClosedException.class, () -> in.close()); } } // Truncated stream (missing closing CRLF) @Test void testCorruptChunkedInputStreamTruncatedCRLF() throws IOException { final String s = "5\r\n01234"; final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); try (ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) { final byte[] tmp = new byte[5]; Assertions.assertEquals(5, in.read(tmp)); Assertions.assertThrows(MalformedChunkCodingException.class, () -> in.read()); } } // Missing \r\n at the end of the first chunk @Test void testCorruptChunkedInputStreamMissingCRLF() throws IOException { final String s = "5\r\n012345\r\n56789\r\n0\r\n"; final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); try (ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) { final byte[] buffer = new byte[300]; final ByteArrayOutputStream out = new ByteArrayOutputStream(); Assertions.assertThrows(MalformedChunkCodingException.class, () -> { int len; while ((len = in.read(buffer)) > 0) { out.write(buffer, 0, len); } }); } } // Missing LF @Test void testCorruptChunkedInputStreamMissingLF() throws IOException { final String s = "5\r01234\r\n5\r\n56789\r\n0\r\n"; final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); try (ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) { Assertions.assertThrows(MalformedChunkCodingException.class, in::read); } } // Invalid chunk size @Test void testCorruptChunkedInputStreamInvalidSize() throws IOException { final String s = "whatever\r\n01234\r\n5\r\n56789\r\n0\r\n"; final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); try (ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) { Assertions.assertThrows(MalformedChunkCodingException.class, in::read); } } // Negative chunk size @Test void testCorruptChunkedInputStreamNegativeSize() throws IOException { final String s = "-5\r\n01234\r\n5\r\n56789\r\n0\r\n"; final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); try (ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) { Assertions.assertThrows(MalformedChunkCodingException.class, in::read); } } // Truncated chunk @Test void testCorruptChunkedInputStreamTruncatedChunk() throws IOException { final String s = "3\r\n12"; final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); try (ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) { final byte[] buffer = new byte[300]; Assertions.assertEquals(2, in.read(buffer)); Assertions.assertThrows(MalformedChunkCodingException.class, () -> in.read(buffer)); } } // Invalid footer @Test void testCorruptChunkedInputStreamInvalidFooter() throws IOException { final String s = "1\r\n0\r\n0\r\nstuff\r\n"; final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); try (ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) { in.read(); Assertions.assertThrows(MalformedChunkCodingException.class, in::read); } } @Test void testCorruptChunkedInputStreamClose() throws IOException { final String s = "whatever\r\n01234\r\n5\r\n56789\r\n0\r\n"; final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); try (final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) { Assertions.assertThrows(MalformedChunkCodingException.class, in::read); } } @Test void testEmptyChunkedInputStream() throws IOException { final String s = "0\r\n"; final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); try (ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) { final byte[] buffer = new byte[300]; final ByteArrayOutputStream out = new ByteArrayOutputStream(); int len; while ((len = in.read(buffer)) > 0) { out.write(buffer, 0, len); } Assertions.assertEquals(0, out.size()); } } @Test void testTooLongChunkHeader() throws IOException { final String s = "5; and some very looooong commend\r\n12345\r\n0\r\n"; final SessionInputBuffer inBuffer1 = new SessionInputBufferImpl(16); final byte[] buffer = new byte[300]; final ByteArrayInputStream inputStream1 = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); try (final ChunkedInputStream in1 = new ChunkedInputStream(inBuffer1, inputStream1)) { Assertions.assertEquals(5, in1.read(buffer)); } final SessionInputBuffer inBuffer2 = new SessionInputBufferImpl(16, 10); final ByteArrayInputStream inputStream2 = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final ChunkedInputStream in2 = new ChunkedInputStream(inBuffer2, inputStream2); Assertions.assertThrows(MessageConstraintException.class, () -> in2.read(buffer)); // close() would throw here } @Test void testChunkedOutputStreamClose() throws IOException { final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2048); out.close(); out.close(); Assertions.assertThrows(IOException.class, () -> out.write(new byte[] {1,2,3})); Assertions.assertThrows(IOException.class, () -> out.write(1)); } @Test void testChunkedConsistence() throws IOException { final String input = "76126;27823abcd;:q38a-\nkjc\rk%1ad\tkh/asdui\r\njkh+?\\suweb"; final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try (ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2048)) { out.write(input.getBytes(StandardCharsets.US_ASCII)); out.flush(); out.close(); } final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); try (ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) { final byte[] d = new byte[10]; final ByteArrayOutputStream result = new ByteArrayOutputStream(); int len = 0; while ((len = in.read(d)) > 0) { result.write(d, 0, len); } final String output = new String(result.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals(input, output); } } @Test void testChunkedOutputStreamWithTrailers() throws IOException { final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try (ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2, () -> Arrays.asList( new BasicHeader("E", ""), new BasicHeader("Y", "Z")) )) { out.write('x'); out.finish(); } final String content = new String(outputStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("1\r\nx\r\n0\r\nE: \r\nY: Z\r\n\r\n", content); } @Test void testChunkedOutputStream() throws IOException { final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try (ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2)) { out.write('1'); out.write('2'); out.write('3'); out.write('4'); out.finish(); } final String content = new String(outputStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("2\r\n12\r\n2\r\n34\r\n0\r\n\r\n", content); } @Test void testChunkedOutputStreamLargeChunk() throws IOException { final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try (ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2)) { out.write(new byte[] { '1', '2', '3', '4' }); out.finish(); } final String content = new String(outputStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("4\r\n1234\r\n0\r\n\r\n", content); } @Test void testChunkedOutputStreamSmallChunk() throws IOException { final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try (ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2)) { out.write('1'); out.finish(); } final String content = new String(outputStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("1\r\n1\r\n0\r\n\r\n", content); } @Test void testResumeOnSocketTimeoutInData() throws IOException { final String s = "5\r\n01234\r\n5\r\n5\0006789\r\na\r\n0123\000456789\r\n0\r\n"; final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); try (TimeoutByteArrayInputStream inputStream = new TimeoutByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) { final byte[] tmp = new byte[3]; int bytesRead = 0; int timeouts = 0; int i = 0; while (i != -1) { try { i = in.read(tmp); if (i > 0) { bytesRead += i; } } catch (final InterruptedIOException ex) { timeouts++; } } Assertions.assertEquals(20, bytesRead); Assertions.assertEquals(2, timeouts); } } @Test void testResumeOnSocketTimeoutInChunk() throws IOException { final String s = "5\000\r\000\n\00001234\r\n\0005\r\n56789\r\na\r\n0123456789\r\n\0000\r\n"; final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); try (TimeoutByteArrayInputStream inputStream = new TimeoutByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) { final byte[] tmp = new byte[3]; int bytesRead = 0; int timeouts = 0; int i = 0; while (i != -1) { try { i = in.read(tmp); if (i > 0) { bytesRead += i; } } catch (final InterruptedIOException ex) { timeouts++; } } Assertions.assertEquals(20, bytesRead); Assertions.assertEquals(5, timeouts); } } // Test for when buffer is larger than chunk size @Test void testHugeChunk() throws IOException { final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream("1234567890abcdef\r\n01234567".getBytes( StandardCharsets.US_ASCII)); final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream); final ByteArrayOutputStream out = new ByteArrayOutputStream(); for (int i = 0; i < 8; ++i) { out.write(in.read()); } final String result = new String(out.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("01234567", result); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestContentLengthInputStream.java0100644 0000000 0000000 00000014221 00000250600 030610 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.StreamClosedException; import org.apache.hc.core5.http.io.SessionInputBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestContentLengthInputStream { @Test void testBasics() throws IOException { final String s = "1234567890123456"; final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); try (InputStream in = new ContentLengthInputStream(inBuffer, inputStream, 10L)) { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final byte[] buffer = new byte[50]; int len = in.read(buffer, 0, 2); outputStream.write(buffer, 0, len); len = in.read(buffer); outputStream.write(buffer, 0, len); final String result = new String(outputStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("1234567890", result); } } @Test void testSkip() throws IOException { final ByteArrayInputStream inputStream1 = new ByteArrayInputStream(new byte[20]); final SessionInputBuffer inBuffer1 = new SessionInputBufferImpl(16); try (InputStream in1 = new ContentLengthInputStream(inBuffer1, inputStream1, 10L)) { Assertions.assertEquals(10, in1.skip(10)); Assertions.assertEquals(-1, in1.read()); } final ByteArrayInputStream inputStream2 = new ByteArrayInputStream(new byte[20]); final SessionInputBuffer inBuffer2 = new SessionInputBufferImpl(16); try (InputStream in2 = new ContentLengthInputStream(inBuffer2, inputStream2, 10L)) { in2.read(); Assertions.assertEquals(9, in2.skip(10)); Assertions.assertEquals(-1, in2.read()); } final ByteArrayInputStream inputStream3 = new ByteArrayInputStream(new byte[20]); final SessionInputBuffer inBuffer3 = new SessionInputBufferImpl(16); try (InputStream in3 = new ContentLengthInputStream(inBuffer3, inputStream3, 2L)) { in3.read(); in3.read(); Assertions.assertTrue(in3.skip(10) <= 0); Assertions.assertEquals(0, in3.skip(-1)); Assertions.assertEquals(-1, in3.read()); } final ByteArrayInputStream inputStream4 = new ByteArrayInputStream(new byte[20]); final SessionInputBuffer inBuffer4 = new SessionInputBufferImpl(16); try (InputStream in4 = new ContentLengthInputStream(inBuffer4, inputStream4, 10L)) { Assertions.assertEquals(5, in4.skip(5)); Assertions.assertEquals(5, in4.read(new byte[20])); } } @Test void testAvailable() throws IOException { final ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[] {1, 2, 3}); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); try (InputStream in = new ContentLengthInputStream(inBuffer, inputStream, 3L)) { Assertions.assertEquals(0, in.available()); in.read(); Assertions.assertEquals(2, in.available()); } } @Test void testClose() throws IOException { final String s = "1234567890123456-"; final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final InputStream in = new ContentLengthInputStream(inBuffer, inputStream, 16L); in.close(); in.close(); Assertions.assertThrows(StreamClosedException.class, in::read); final byte[] tmp = new byte[10]; Assertions.assertThrows(StreamClosedException.class, () -> in.read(tmp)); Assertions.assertThrows(StreamClosedException.class, () -> in.read(tmp, 0, tmp.length)); Assertions.assertEquals('-', inBuffer.read(inputStream)); } @Test void testTruncatedContent() throws IOException { final String s = "1234567890123456"; final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final InputStream in = new ContentLengthInputStream(inBuffer, inputStream, 32L); final byte[] tmp = new byte[32]; final int byteRead = in.read(tmp); Assertions.assertEquals(16, byteRead); Assertions.assertThrows(ConnectionClosedException.class, () -> in.read(tmp)); Assertions.assertThrows(ConnectionClosedException.class, () -> in.read()); Assertions.assertThrows(ConnectionClosedException.class, () -> in.close()); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestContentLengthOutputStream.java0100644 0000000 0000000 00000005353 00000250600 031017 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.ByteArrayOutputStream; import java.io.OutputStream; import org.apache.hc.core5.http.StreamClosedException; import org.apache.hc.core5.http.io.SessionOutputBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestContentLengthOutputStream { @Test void testBasics() throws Exception { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); try (OutputStream out = new ContentLengthOutputStream(outbuffer, outputStream, 15L)) { final byte[] tmp = new byte[10]; out.write(tmp, 0, 10); out.write(1); out.write(tmp, 0, 10); out.write(tmp, 0, 10); out.write(tmp); out.write(1); out.write(2); out.flush(); } final byte[] data = outputStream.toByteArray(); Assertions.assertEquals(15, data.length); } @Test void testClose() throws Exception { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final OutputStream out = new ContentLengthOutputStream(outbuffer, outputStream, 15L); out.close(); out.close(); final byte[] tmp = new byte[10]; Assertions.assertThrows(StreamClosedException.class, () -> out.write(tmp)); Assertions.assertThrows(StreamClosedException.class, () -> out.write(1)); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestDefaultBHttpClientConnection.java0100644 0000000 0000000 00000042175 00000250600 031356 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.Socket; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.LengthRequiredException; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.NotImplementedException; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy; import org.apache.hc.core5.http.io.entity.ByteArrayEntity; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; class TestDefaultBHttpClientConnection { @Mock private Socket socket; private DefaultBHttpClientConnection conn; @BeforeEach void prepareMocks() { MockitoAnnotations.openMocks(this); conn = new DefaultBHttpClientConnection(Http1Config.DEFAULT, null, null, DefaultContentLengthStrategy.INSTANCE, DefaultContentLengthStrategy.INSTANCE, DefaultHttpRequestWriterFactory.INSTANCE, DefaultHttpResponseParserFactory.INSTANCE); } @Test void testBasics() { Assertions.assertFalse(conn.isOpen()); Assertions.assertEquals("[Not bound]", conn.toString()); } @Test void testReadResponseHead() throws Exception { final String s = "HTTP/1.1 200 OK\r\nUser-Agent: test\r\n\r\n"; final ByteArrayInputStream inStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); Mockito.when(socket.getInputStream()).thenReturn(inStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount()); final ClassicHttpResponse response = conn.receiveResponseHeader(); Assertions.assertNotNull(response); Assertions.assertEquals(200, response.getCode()); Assertions.assertTrue(response.containsHeader("User-Agent")); Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount()); } @Test void testReadResponseEntityWithoutContentLength() throws Exception { final String s = "HTTP/1.1 200 OK\r\nServer: test\r\n\r\n123"; final ByteArrayInputStream inStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); Mockito.when(socket.getInputStream()).thenReturn(inStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount()); final ClassicHttpResponse response = conn.receiveResponseHeader(); Assertions.assertNotNull(response); Assertions.assertEquals(200, response.getCode()); Assertions.assertTrue(response.containsHeader("Server")); Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount()); conn.receiveResponseEntity(response); final HttpEntity entity = response.getEntity(); Assertions.assertNotNull(entity); Assertions.assertEquals(-1, entity.getContentLength()); Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount()); final InputStream content = entity.getContent(); Assertions.assertNotNull(content); Assertions.assertEquals(3, content.available()); Assertions.assertEquals('1', content.read()); Assertions.assertEquals('2', content.read()); Assertions.assertEquals('3', content.read()); } @Test void testReadResponseEntityWithContentLength() throws Exception { final String s = "HTTP/1.1 200 OK\r\nServer: test\r\nContent-Length: 3\r\n\r\n123"; final ByteArrayInputStream inStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); Mockito.when(socket.getInputStream()).thenReturn(inStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount()); final ClassicHttpResponse response = conn.receiveResponseHeader(); Assertions.assertNotNull(response); Assertions.assertEquals(200, response.getCode()); Assertions.assertTrue(response.containsHeader("Server")); Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount()); conn.receiveResponseEntity(response); final HttpEntity entity = response.getEntity(); Assertions.assertNotNull(entity); Assertions.assertEquals(3, entity.getContentLength()); Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount()); final InputStream content = entity.getContent(); Assertions.assertNotNull(content); Assertions.assertTrue(content instanceof ContentLengthInputStream); } @Test void testReadResponseEntityChunkCoded() throws Exception { final String s = "HTTP/1.1 200 OK\r\nServer: test\r\nTransfer-Encoding: " + "chunked\r\n\r\n3\r\n123\r\n0\r\n\r\n"; final ByteArrayInputStream inStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); Mockito.when(socket.getInputStream()).thenReturn(inStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount()); final ClassicHttpResponse response = conn.receiveResponseHeader(); Assertions.assertNotNull(response); Assertions.assertEquals(200, response.getCode()); Assertions.assertTrue(response.containsHeader("Server")); Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount()); conn.receiveResponseEntity(response); final HttpEntity entity = response.getEntity(); Assertions.assertNotNull(entity); Assertions.assertEquals(-1, entity.getContentLength()); Assertions.assertTrue(entity.isChunked()); Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount()); final InputStream content = entity.getContent(); Assertions.assertNotNull(content); Assertions.assertTrue(content instanceof ChunkedInputStream); } @Test void testReadResponseEntityIdentity() throws Exception { final String s = "HTTP/1.1 200 OK\r\nServer: test\r\nTransfer-Encoding: identity\r\n\r\n123"; final ByteArrayInputStream inStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); Mockito.when(socket.getInputStream()).thenReturn(inStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount()); final ClassicHttpResponse response = conn.receiveResponseHeader(); Assertions.assertNotNull(response); Assertions.assertEquals(200, response.getCode()); Assertions.assertTrue(response.containsHeader("Server")); Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount()); Assertions.assertThrows(NotImplementedException.class, () -> conn.receiveResponseEntity(response)); } @Test void testReadResponseNoEntity() throws Exception { final String s = "HTTP/1.1 200 OK\r\nServer: test\r\n\r\n"; final ByteArrayInputStream inStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); Mockito.when(socket.getInputStream()).thenReturn(inStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount()); final ClassicHttpResponse response = conn.receiveResponseHeader(); Assertions.assertNotNull(response); Assertions.assertEquals(200, response.getCode()); Assertions.assertTrue(response.containsHeader("Server")); Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount()); conn.receiveResponseEntity(response); final HttpEntity entity = response.getEntity(); Assertions.assertNotNull(entity); final InputStream content = entity.getContent(); Assertions.assertNotNull(content); Assertions.assertTrue(content instanceof IdentityInputStream); } @Test void testWriteRequestHead() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount()); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/stuff"); request.addHeader("User-Agent", "test"); conn.sendRequestHeader(request); conn.flush(); Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount()); final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("GET /stuff HTTP/1.1\r\nUser-Agent: test\r\n\r\n", s); } @Test void testWriteRequestEntityWithContentLength() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount()); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/stuff"); request.addHeader("User-Agent", "test"); request.addHeader("Content-Length", "3"); request.setEntity(new StringEntity("123", ContentType.TEXT_PLAIN)); conn.sendRequestHeader(request); conn.sendRequestEntity(request); conn.flush(); Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount()); final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("POST /stuff HTTP/1.1\r\nUser-Agent: test\r\nContent-Length: 3\r\n\r\n123", s); } @Test void testWriteRequestEntityChunkCoded() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount()); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/stuff"); request.addHeader("User-Agent", "test"); request.addHeader("Transfer-Encoding", "chunked"); request.setEntity(new StringEntity("123", ContentType.TEXT_PLAIN)); conn.sendRequestHeader(request); conn.sendRequestEntity(request); conn.flush(); Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount()); final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("POST /stuff HTTP/1.1\r\nUser-Agent: test\r\nTransfer-Encoding: " + "chunked\r\n\r\n3\r\n123\r\n0\r\n\r\n", s); } @Test void testWriteRequestEntityNoContentLength() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount()); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/stuff"); request.addHeader("User-Agent", "test"); request.setEntity(new StringEntity("123", ContentType.TEXT_PLAIN)); conn.sendRequestHeader(request); Assertions.assertThrows(LengthRequiredException.class, () -> conn.sendRequestEntity(request)); } @Test void testWriteRequestNoEntity() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount()); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/stuff"); request.addHeader("User-Agent", "test"); conn.sendRequestHeader(request); conn.sendRequestEntity(request); conn.flush(); Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount()); final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("POST /stuff HTTP/1.1\r\nUser-Agent: test\r\n\r\n", s); } @Test void testTerminateRequestChunkedEntity() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount()); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/stuff"); request.addHeader("User-Agent", "test"); request.addHeader("Transfer-Encoding", "chunked"); final StringEntity entity = new StringEntity("123", ContentType.TEXT_PLAIN, true); request.setEntity(entity); conn.sendRequestHeader(request); conn.terminateRequest(request); conn.flush(); Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount()); final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("POST /stuff HTTP/1.1\r\nUser-Agent: test\r\nTransfer-Encoding: " + "chunked\r\n\r\n0\r\n\r\n", s); Assertions.assertTrue(conn.isConsistent()); } @Test void testTerminateRequestContentLengthShort() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount()); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/stuff"); request.addHeader("User-Agent", "test"); request.addHeader("Content-Length", "3"); final StringEntity entity = new StringEntity("123", ContentType.TEXT_PLAIN, true); request.setEntity(entity); conn.sendRequestHeader(request); conn.terminateRequest(request); conn.flush(); Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount()); final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("POST /stuff HTTP/1.1\r\nUser-Agent: test\r\nContent-Length: " + "3\r\n\r\n123", s); Assertions.assertTrue(conn.isConsistent()); } @Test void testTerminateRequestContentLengthLong() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount()); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/stuff"); request.addHeader("User-Agent", "test"); request.addHeader("Content-Length", "3000"); final ByteArrayEntity entity = new ByteArrayEntity(new byte[3000], ContentType.TEXT_PLAIN, true); request.setEntity(entity); conn.sendRequestHeader(request); conn.terminateRequest(request); conn.flush(); Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount()); final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("POST /stuff HTTP/1.1\r\nUser-Agent: test\r\nContent-Length: " + "3000\r\n\r\n", s); Assertions.assertFalse(conn.isConsistent()); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestDefaultBHttpServerConnection.java0100644 0000000 0000000 00000033635 00000250600 031407 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.Socket; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.NotImplementedException; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; class TestDefaultBHttpServerConnection { @Mock private Socket socket; private DefaultBHttpServerConnection conn; @BeforeEach void prepareMocks() { MockitoAnnotations.openMocks(this); conn = new DefaultBHttpServerConnection("http", Http1Config.DEFAULT, null, null, DefaultContentLengthStrategy.INSTANCE, DefaultContentLengthStrategy.INSTANCE, DefaultHttpRequestParserFactory.INSTANCE, DefaultHttpResponseWriterFactory.INSTANCE); } @Test void testBasics() { Assertions.assertFalse(conn.isOpen()); Assertions.assertEquals("[Not bound]", conn.toString()); } @Test void testReadRequestHead() throws Exception { final String s = "GET / HTTP/1.1\r\nUser-Agent: test\r\n\r\n"; final ByteArrayInputStream inStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); Mockito.when(socket.getInputStream()).thenReturn(inStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount()); final ClassicHttpRequest request = conn.receiveRequestHeader(); Assertions.assertNotNull(request); Assertions.assertEquals("/", request.getPath()); Assertions.assertEquals(Method.GET.name(), request.getMethod()); Assertions.assertTrue(request.containsHeader("User-Agent")); Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount()); Assertions.assertNull(conn.getEndpointDetails().getRemoteAddress()); Assertions.assertNull(conn.getEndpointDetails().getLocalAddress()); Assertions.assertEquals(Timeout.ofMilliseconds(0), conn.getEndpointDetails().getSocketTimeout()); Assertions.assertEquals("null<->null", conn.getEndpointDetails().toString()); } @Test void testReadRequestEntityWithContentLength() throws Exception { final String s = "POST / HTTP/1.1\r\nUser-Agent: test\r\nContent-Length: 3\r\n\r\n123"; final ByteArrayInputStream inStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); Mockito.when(socket.getInputStream()).thenReturn(inStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount()); final ClassicHttpRequest request = conn.receiveRequestHeader(); Assertions.assertNotNull(request); Assertions.assertEquals("/", request.getPath()); Assertions.assertEquals(Method.POST.name(), request.getMethod()); Assertions.assertTrue(request.containsHeader("User-Agent")); Assertions.assertNull(request.getEntity()); Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount()); conn.receiveRequestEntity(request); final HttpEntity entity = request.getEntity(); Assertions.assertNotNull(entity); Assertions.assertEquals(3, entity.getContentLength()); Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount()); final InputStream content = entity.getContent(); Assertions.assertNotNull(content); Assertions.assertTrue(content instanceof ContentLengthInputStream); } @Test void testReadRequestEntityChunckCoded() throws Exception { final String s = "POST /stuff HTTP/1.1\r\nUser-Agent: test\r\nTransfer-Encoding: " + "chunked\r\n\r\n3\r\n123\r\n0\r\n\r\n"; final ByteArrayInputStream inStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); Mockito.when(socket.getInputStream()).thenReturn(inStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount()); final ClassicHttpRequest request = conn.receiveRequestHeader(); Assertions.assertNotNull(request); Assertions.assertEquals("/stuff", request.getPath()); Assertions.assertEquals(Method.POST.name(), request.getMethod()); Assertions.assertTrue(request.containsHeader("User-Agent")); Assertions.assertNull(request.getEntity()); Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount()); conn.receiveRequestEntity(request); final HttpEntity entity = request.getEntity(); Assertions.assertNotNull(entity); Assertions.assertEquals(-1, entity.getContentLength()); Assertions.assertTrue(entity.isChunked()); Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount()); final InputStream content = entity.getContent(); Assertions.assertNotNull(content); Assertions.assertTrue(content instanceof ChunkedInputStream); } @Test void testReadRequestEntityIdentity() throws Exception { final String s = "POST /stuff HTTP/1.1\r\nUser-Agent: test\r\nTransfer-Encoding: " + "identity\r\n\r\n123"; final ByteArrayInputStream inStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); Mockito.when(socket.getInputStream()).thenReturn(inStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount()); final ClassicHttpRequest request = conn.receiveRequestHeader(); Assertions.assertNotNull(request); Assertions.assertEquals("/stuff", request.getPath()); Assertions.assertEquals(Method.POST.name(), request.getMethod()); Assertions.assertTrue(request.containsHeader("User-Agent")); Assertions.assertNull(request.getEntity()); Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount()); Assertions.assertThrows(ProtocolException.class, () -> conn.receiveRequestEntity(request)); } @Test void testReadRequestNoEntity() throws Exception { final String s = "POST /stuff HTTP/1.1\r\nUser-Agent: test\r\n\r\n"; final ByteArrayInputStream inStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); Mockito.when(socket.getInputStream()).thenReturn(inStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount()); final ClassicHttpRequest request = conn.receiveRequestHeader(); Assertions.assertNotNull(request); Assertions.assertEquals("/stuff", request.getPath()); Assertions.assertEquals(Method.POST.name(), request.getMethod()); Assertions.assertTrue(request.containsHeader("User-Agent")); Assertions.assertNull(request.getEntity()); Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount()); conn.receiveRequestEntity(request); final HttpEntity entity = request.getEntity(); Assertions.assertNull(entity); } @Test void testWriteResponseHead() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount()); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); response.addHeader("User-Agent", "test"); conn.sendResponseHeader(response); conn.flush(); Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount()); final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("HTTP/1.1 200 OK\r\nUser-Agent: test\r\n\r\n", s); } @Test void testWriteResponse100Head() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount()); final ClassicHttpResponse response = new BasicClassicHttpResponse(100, "Go on"); conn.sendResponseHeader(response); conn.flush(); Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount()); final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("HTTP/1.1 100 Go on\r\n\r\n", s); } @Test void testWriteResponseEntityWithContentLength() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount()); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); response.addHeader("Server", "test"); response.addHeader("Content-Length", "3"); response.setEntity(new StringEntity("123", ContentType.TEXT_PLAIN)); conn.sendResponseHeader(response); conn.sendResponseEntity(response); conn.flush(); Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount()); final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("HTTP/1.1 200 OK\r\nServer: test\r\nContent-Length: 3\r\n\r\n123", s); } @Test void testWriteResponseEntityChunkCoded() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount()); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); response.addHeader("Server", "test"); response.addHeader("Transfer-Encoding", "chunked"); response.setEntity(new StringEntity("123", ContentType.TEXT_PLAIN)); conn.sendResponseHeader(response); conn.sendResponseEntity(response); conn.flush(); Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount()); final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("HTTP/1.1 200 OK\r\nServer: test\r\nTransfer-Encoding: " + "chunked\r\n\r\n3\r\n123\r\n0\r\n\r\n", s); } @Test void testWriteResponseEntityIdentity() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount()); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); response.addHeader("Server", "test"); response.addHeader("Transfer-Encoding", "identity"); response.setEntity(new StringEntity("123", ContentType.TEXT_PLAIN)); conn.sendResponseHeader(response); Assertions.assertThrows(NotImplementedException.class, () -> conn.sendResponseEntity(response)); conn.flush(); } @Test void testWriteResponseNoEntity() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount()); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); response.addHeader("Server", "test"); conn.sendResponseHeader(response); conn.sendResponseEntity(response); conn.flush(); Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount()); final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("HTTP/1.1 200 OK\r\nServer: test\r\n\r\n", s); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestHttpRequestExecutor.java0100644 0000000 0000000 00000052450 00000250600 027655 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.io.HttpClientConnection; import org.apache.hc.core5.http.io.HttpResponseInformationCallback; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import java.io.IOException; import java.util.List; class TestHttpRequestExecutor { @Test void testInvalidInput() { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpProcessor httprocessor = Mockito.mock(HttpProcessor.class); Assertions.assertThrows(NullPointerException.class, () -> { final HttpRequestExecutor executor = new HttpRequestExecutor(); executor.execute(null, conn, context); }); Assertions.assertThrows(NullPointerException.class, () -> { final HttpRequestExecutor executor = new HttpRequestExecutor(); executor.execute(request, null, context); }); Assertions.assertThrows(NullPointerException.class, () -> { final HttpRequestExecutor executor = new HttpRequestExecutor(); executor.execute(request, conn, null); }); Assertions.assertThrows(NullPointerException.class, () -> { final HttpRequestExecutor executor = new HttpRequestExecutor(); executor.preProcess(null, httprocessor, context); }); Assertions.assertThrows(NullPointerException.class, () -> { final HttpRequestExecutor executor = new HttpRequestExecutor(); executor.preProcess(request, null, context); }); Assertions.assertThrows(NullPointerException.class, () -> { final HttpRequestExecutor executor = new HttpRequestExecutor(); executor.preProcess(request, httprocessor, null); }); Assertions.assertThrows(NullPointerException.class, () -> { final HttpRequestExecutor executor = new HttpRequestExecutor(); executor.postProcess(null, httprocessor, context); }); Assertions.assertThrows(NullPointerException.class, () -> { final HttpRequestExecutor executor = new HttpRequestExecutor(); executor.postProcess(response, null, context); }); Assertions.assertThrows(NullPointerException.class, () -> { final HttpRequestExecutor executor = new HttpRequestExecutor(); executor.postProcess(response, httprocessor, null); }); } @Test void testBasicExecution() throws Exception { final HttpProcessor httprocessor = Mockito.mock(HttpProcessor.class); final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); executor.preProcess(request, httprocessor, context); Mockito.verify(httprocessor).process(request, request.getEntity(), context); Mockito.when(conn.receiveResponseHeader()).thenReturn( new BasicClassicHttpResponse(200, "OK")); final ClassicHttpResponse response = executor.execute(request, conn, context); Mockito.verify(conn).sendRequestHeader(request); Mockito.verify(conn).flush(); Mockito.verify(conn).receiveResponseHeader(); Mockito.verify(conn).receiveResponseEntity(response); executor.postProcess(response, httprocessor, context); Mockito.verify(httprocessor).process(response, response.getEntity(), context); Assertions.assertSame(request, context.getRequest()); Assertions.assertSame(response, context.getResponse()); } @Test void testExecutionSkipIntermediateResponses() throws Exception { final HttpProcessor httprocessor = Mockito.mock(HttpProcessor.class); final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); executor.preProcess(request, httprocessor, context); Mockito.verify(httprocessor).process(request, request.getEntity(), context); Mockito.when(conn.receiveResponseHeader()).thenReturn( new BasicClassicHttpResponse(100, "Continue"), new BasicClassicHttpResponse(110, "Huh?"), new BasicClassicHttpResponse(111, "Huh?"), new BasicClassicHttpResponse(200, "OK")); final HttpResponseInformationCallback callback = Mockito.mock(HttpResponseInformationCallback.class); final ClassicHttpResponse response = executor.execute(request, conn, callback, context); Mockito.verify(conn).sendRequestHeader(request); Mockito.verify(conn).flush(); Mockito.verify(conn, Mockito.times(4)).receiveResponseHeader(); Mockito.verify(conn, Mockito.times(1)).receiveResponseEntity(response); final ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(HttpResponse.class); Mockito.verify(callback, Mockito.times(2)).execute(responseCaptor.capture(), ArgumentMatchers.eq(conn), ArgumentMatchers.eq(context)); final List infos = responseCaptor.getAllValues(); Assertions.assertNotNull(infos); Assertions.assertEquals(2, infos.size()); final HttpResponse info1 = infos.get(0); Assertions.assertNotNull(info1); Assertions.assertEquals(110, info1.getCode()); final HttpResponse info2 = infos.get(1); Assertions.assertNotNull(info2); Assertions.assertEquals(111, info2.getCode()); executor.postProcess(response, httprocessor, context); Mockito.verify(httprocessor).process(response, response.getEntity(), context); Assertions.assertSame(request, context.getRequest()); Assertions.assertSame(response, context.getResponse()); } @Test void testExecutionNoResponseBody() throws Exception { final HttpProcessor httprocessor = Mockito.mock(HttpProcessor.class); final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); executor.preProcess(request, httprocessor, context); Mockito.verify(httprocessor).process(request, request.getEntity(), context); Mockito.when(conn.receiveResponseHeader()).thenReturn( new BasicClassicHttpResponse(204, "OK")); final ClassicHttpResponse response = executor.execute(request, conn, context); Mockito.verify(conn).sendRequestHeader(request); Mockito.verify(conn).flush(); Mockito.verify(conn).receiveResponseHeader(); Mockito.verify(conn, Mockito.never()).receiveResponseEntity(response); executor.postProcess(response, httprocessor, context); Mockito.verify(httprocessor).process(response, response.getEntity(), context); } @Test void testExecutionHead() throws Exception { final HttpProcessor httprocessor = Mockito.mock(HttpProcessor.class); final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.HEAD, "/"); executor.preProcess(request, httprocessor, context); Mockito.verify(httprocessor).process(request, request.getEntity(), context); Mockito.when(conn.receiveResponseHeader()).thenReturn( new BasicClassicHttpResponse(200, "OK")); final ClassicHttpResponse response = executor.execute(request, conn, context); Mockito.verify(conn).sendRequestHeader(request); Mockito.verify(conn).flush(); Mockito.verify(conn).receiveResponseHeader(); Mockito.verify(conn, Mockito.never()).receiveResponseEntity(response); executor.postProcess(response, httprocessor, context); Mockito.verify(httprocessor).process(response, response.getEntity(), context); } @Test void testExecutionEntityEnclosingRequest() throws Exception { final HttpProcessor httprocessor = Mockito.mock(HttpProcessor.class); final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); final HttpEntity entity = Mockito.mock(HttpEntity.class); request.setEntity(entity); executor.preProcess(request, httprocessor, context); Mockito.verify(httprocessor).process(request, request.getEntity(), context); Mockito.when(conn.receiveResponseHeader()).thenReturn( new BasicClassicHttpResponse(200, "OK")); final ClassicHttpResponse response = executor.execute(request, conn, context); Mockito.verify(conn).sendRequestHeader(request); Mockito.verify(conn).sendRequestEntity(request); Mockito.verify(conn).flush(); Mockito.verify(conn).receiveResponseHeader(); Mockito.verify(conn).receiveResponseEntity(response); executor.postProcess(response, httprocessor, context); Mockito.verify(httprocessor).process(response, response.getEntity(), context); } @Test void testExecutionEntityEnclosingRequestWithExpectContinueSuccess() throws Exception { final HttpProcessor httprocessor = Mockito.mock(HttpProcessor.class); final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.addHeader(HttpHeaders.EXPECT, HeaderElements.CONTINUE); final HttpEntity entity = Mockito.mock(HttpEntity.class); request.setEntity(entity); executor.preProcess(request, httprocessor, context); Mockito.verify(httprocessor).process(request, request.getEntity(), context); Mockito.when(conn.receiveResponseHeader()).thenReturn( new BasicClassicHttpResponse(100, "Continue"), new BasicClassicHttpResponse(200, "OK")); Mockito.when(conn.isDataAvailable(ArgumentMatchers.any(Timeout.class))).thenReturn(Boolean.TRUE); final ClassicHttpResponse response = executor.execute(request, conn, context); Mockito.verify(conn).sendRequestHeader(request); Mockito.verify(conn).sendRequestEntity(request); Mockito.verify(conn, Mockito.times(2)).flush(); Mockito.verify(conn).isDataAvailable(Timeout.ofMilliseconds(3000)); Mockito.verify(conn, Mockito.times(2)).receiveResponseHeader(); Mockito.verify(conn).receiveResponseEntity(response); executor.postProcess(response, httprocessor, context); Mockito.verify(httprocessor).process(response, response.getEntity(), context); } @Test void testExecutionEntityEnclosingRequestWithExpectContinueFailure() throws Exception { final HttpProcessor httprocessor = Mockito.mock(HttpProcessor.class); final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.addHeader(HttpHeaders.EXPECT, HeaderElements.CONTINUE); final HttpEntity entity = Mockito.mock(HttpEntity.class); request.setEntity(entity); executor.preProcess(request, httprocessor, context); Mockito.verify(httprocessor).process(request, request.getEntity(), context); Mockito.when(conn.receiveResponseHeader()).thenReturn( new BasicClassicHttpResponse(402, "OK")); Mockito.when(conn.isDataAvailable(ArgumentMatchers.any(Timeout.class))).thenReturn(Boolean.TRUE); final ClassicHttpResponse response = executor.execute(request, conn, context); Mockito.verify(conn).sendRequestHeader(request); Mockito.verify(conn, Mockito.never()).sendRequestEntity(request); Mockito.verify(conn).terminateRequest(request); Mockito.verify(conn, Mockito.times(2)).flush(); Mockito.verify(conn).isDataAvailable(Timeout.ofMilliseconds(3000)); Mockito.verify(conn).receiveResponseHeader(); Mockito.verify(conn).receiveResponseEntity(response); executor.postProcess(response, httprocessor, context); Mockito.verify(httprocessor).process(response, response.getEntity(), context); } @Test void testExecutionEntityEnclosingRequestWithExpectContinueMultiple1xxResponses() throws Exception { final HttpProcessor httprocessor = Mockito.mock(HttpProcessor.class); final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.addHeader(HttpHeaders.EXPECT, HeaderElements.CONTINUE); final HttpEntity entity = Mockito.mock(HttpEntity.class); request.setEntity(entity); executor.preProcess(request, httprocessor, context); Mockito.verify(httprocessor).process(request, request.getEntity(), context); Mockito.when(conn.receiveResponseHeader()).thenReturn( new BasicClassicHttpResponse(110, "Huh?"), new BasicClassicHttpResponse(100, "Continue"), new BasicClassicHttpResponse(111, "Huh?"), new BasicClassicHttpResponse(200, "OK")); Mockito.when(conn.isDataAvailable(ArgumentMatchers.any(Timeout.class))).thenReturn(Boolean.TRUE); final HttpResponseInformationCallback callback = Mockito.mock(HttpResponseInformationCallback.class); final ClassicHttpResponse response = executor.execute(request, conn, callback, context); Mockito.verify(conn).sendRequestHeader(request); Mockito.verify(conn).sendRequestEntity(request); Mockito.verify(conn, Mockito.times(2)).flush(); Mockito.verify(conn, Mockito.times(2)).isDataAvailable(Timeout.ofMilliseconds(3000)); Mockito.verify(conn, Mockito.times(4)).receiveResponseHeader(); Mockito.verify(conn).receiveResponseEntity(response); final ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(HttpResponse.class); Mockito.verify(callback, Mockito.times(2)).execute(responseCaptor.capture(), ArgumentMatchers.eq(conn), ArgumentMatchers.eq(context)); final List infos = responseCaptor.getAllValues(); Assertions.assertNotNull(infos); Assertions.assertEquals(2, infos.size()); final HttpResponse info1 = infos.get(0); Assertions.assertNotNull(info1); Assertions.assertEquals(110, info1.getCode()); final HttpResponse info2 = infos.get(1); Assertions.assertNotNull(info2); Assertions.assertEquals(111, info2.getCode()); executor.postProcess(response, httprocessor, context); Mockito.verify(httprocessor).process(response, response.getEntity(), context); } @Test void testExecutionEntityEnclosingRequestWithExpectContinueNoResponse() throws Exception { final HttpProcessor httprocessor = Mockito.mock(HttpProcessor.class); final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.addHeader(HttpHeaders.EXPECT, HeaderElements.CONTINUE); final HttpEntity entity = Mockito.mock(HttpEntity.class); request.setEntity(entity); executor.preProcess(request, httprocessor, context); Mockito.verify(httprocessor).process(request, request.getEntity(), context); Mockito.when(conn.receiveResponseHeader()).thenReturn( new BasicClassicHttpResponse(200, "OK")); Mockito.when(conn.isDataAvailable(ArgumentMatchers.any(Timeout.class))).thenReturn(Boolean.FALSE); final ClassicHttpResponse response = executor.execute(request, conn, context); Mockito.verify(conn).sendRequestHeader(request); Mockito.verify(conn).sendRequestEntity(request); Mockito.verify(conn, Mockito.times(2)).flush(); Mockito.verify(conn).isDataAvailable(Timeout.ofMilliseconds(3000)); Mockito.verify(conn).receiveResponseHeader(); Mockito.verify(conn).receiveResponseEntity(response); executor.postProcess(response, httprocessor, context); Mockito.verify(httprocessor).process(response, response.getEntity(), context); } @Test void testExecutionIOException() throws Exception { final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); Mockito.doThrow(new IOException("Oopsie")).when(conn).sendRequestHeader(request); Assertions.assertThrows(IOException.class, () -> executor.execute(request, conn, context)); Mockito.verify(conn).close(CloseMode.IMMEDIATE); } @Test void testExecutionRuntimeException() throws Exception { final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); Mockito.doThrow(new RuntimeException("Oopsie")).when(conn).receiveResponseHeader(); Assertions.assertThrows(RuntimeException.class, () -> executor.execute(request, conn, context)); Mockito.verify(conn).close(CloseMode.IMMEDIATE); } @Test void testExecutionHttpException() throws Exception { final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); Mockito.doThrow(new HttpException("Oopsie")).when(conn).receiveResponseHeader(); Assertions.assertThrows(HttpException.class, () -> executor.execute(request, conn, context)); Mockito.verify(conn).close(); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestHttpService.java0100644 0000000 0000000 00000040061 00000250600 026101 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.InputStream; import java.util.List; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequestMapper; import org.apache.hc.core5.http.HttpResponseFactory; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.MethodNotSupportedException; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.UnsupportedHttpVersionException; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.HttpServerConnection; import org.apache.hc.core5.http.io.entity.InputStreamEntity; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.Spy; class TestHttpService { @Mock private HttpProcessor httprocessor; @Mock private ConnectionReuseStrategy connReuseStrategy; @Spy private final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK); @Mock private HttpResponseFactory responseFactory; @Mock private HttpRequestMapper handlerResolver; @Mock private HttpRequestHandler requestHandler; @Mock private HttpServerConnection conn; private HttpService httpservice; @BeforeEach void prepareMocks() { MockitoAnnotations.openMocks(this); httpservice = new HttpService( httprocessor, handlerResolver, connReuseStrategy, responseFactory); } @Test void testInvalidInitialization() { Assertions.assertThrows(NullPointerException.class, () -> new HttpService( null, handlerResolver, connReuseStrategy, responseFactory)); } @Test void testBasicExecution() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); Mockito.when(conn.receiveRequestHeader()).thenReturn(request); Mockito.when(responseFactory.newHttpResponse(200)).thenReturn(response); Mockito.when(connReuseStrategy.keepAlive(request, response, context)).thenReturn(Boolean.FALSE); httpservice.handleRequest(conn, context); Assertions.assertEquals(HttpStatus.SC_NOT_IMPLEMENTED, response.getCode()); Assertions.assertSame(request, context.getRequest()); Assertions.assertSame(response, context.getResponse()); Mockito.verify(httprocessor).process(request, request.getEntity(), context); Mockito.verify(httprocessor).process(response, response.getEntity(), context); Mockito.verify(conn).sendResponseHeader(response); Mockito.verify(conn).sendResponseEntity(response); Mockito.verify(conn).flush(); Mockito.verify(conn).close(); Mockito.verify(response).close(); } @Test void testExecutionEntityEnclosingRequest() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); final InputStream inStream = Mockito.mock(InputStream.class); final InputStreamEntity entity = new InputStreamEntity(inStream, -1, null); request.setEntity(entity); Mockito.when(conn.receiveRequestHeader()).thenReturn(request); Mockito.when(responseFactory.newHttpResponse(200)).thenReturn(response); Mockito.when(connReuseStrategy.keepAlive(ArgumentMatchers.eq(request), ArgumentMatchers.argThat(errorResponse -> errorResponse.getCode() == HttpStatus.SC_NOT_IMPLEMENTED), ArgumentMatchers.eq(context))).thenReturn(Boolean.TRUE); httpservice.handleRequest(conn, context); final ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(ClassicHttpResponse.class); Mockito.verify(conn).sendResponseHeader(responseCaptor.capture()); final ClassicHttpResponse response = responseCaptor.getValue(); Assertions.assertNotNull(response); Assertions.assertEquals(HttpStatus.SC_NOT_IMPLEMENTED, response.getCode()); Assertions.assertSame(request, context.getRequest()); Mockito.verify(httprocessor).process(request, request.getEntity(), context); Mockito.verify(inStream).close(); Mockito.verify(httprocessor).process(response, response.getEntity(), context); Mockito.verify(conn).sendResponseHeader(response); Mockito.verify(conn).sendResponseEntity(response); Mockito.verify(conn).flush(); Mockito.verify(conn, Mockito.never()).close(); Mockito.verify(response).close(); } @Test void testExecutionEntityEnclosingRequestWithExpectContinue() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.addHeader(HttpHeaders.EXPECT, HeaderElements.CONTINUE); final InputStream inStream = Mockito.mock(InputStream.class); final InputStreamEntity entity = new InputStreamEntity(inStream, -1, null); request.setEntity(entity); Mockito.when(conn.receiveRequestHeader()).thenReturn(request); Mockito.when(responseFactory.newHttpResponse(200)).thenReturn(response); Mockito.when(connReuseStrategy.keepAlive(ArgumentMatchers.eq(request), ArgumentMatchers.argThat(errorResponse -> errorResponse.getCode() == HttpStatus.SC_NOT_IMPLEMENTED), ArgumentMatchers.eq(context))).thenReturn(Boolean.TRUE); httpservice.handleRequest(conn, context); final ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(ClassicHttpResponse.class); Mockito.verify(conn, Mockito.times(2)).sendResponseHeader(responseCaptor.capture()); final List responses = responseCaptor.getAllValues(); Assertions.assertNotNull(responses); Assertions.assertEquals(2, responses.size()); final ClassicHttpResponse ack = responses.get(0); final ClassicHttpResponse response = responses.get(1); Assertions.assertEquals(HttpStatus.SC_CONTINUE, ack.getCode()); Assertions.assertEquals(HttpStatus.SC_NOT_IMPLEMENTED, response.getCode()); Assertions.assertSame(request, context.getRequest()); Mockito.verify(httprocessor).process(request, request.getEntity(), context); Mockito.verify(inStream).close(); Mockito.verify(httprocessor).process(response, response.getEntity(), context); Mockito.verify(conn).sendResponseHeader(response); Mockito.verify(conn).sendResponseEntity(response); Mockito.verify(conn, Mockito.times(2)).flush(); Mockito.verify(conn, Mockito.never()).close(); Mockito.verify(response).close(); } @Test void testMethodNotSupported() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest("whatever", "/"); Mockito.when(conn.receiveRequestHeader()).thenReturn(request); Mockito.when(responseFactory.newHttpResponse(200)).thenReturn(response); Mockito.when(handlerResolver.resolve(request, context)).thenReturn(requestHandler); Mockito.doThrow(new MethodNotSupportedException("whatever")).when( requestHandler).handle(request, response, context); httpservice.handleRequest(conn, context); final ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(ClassicHttpResponse.class); Mockito.verify(conn).sendResponseHeader(responseCaptor.capture()); final ClassicHttpResponse error = responseCaptor.getValue(); Assertions.assertNotNull(error); Assertions.assertSame(request, context.getRequest()); Assertions.assertEquals(HttpStatus.SC_NOT_IMPLEMENTED, error.getCode()); Mockito.verify(httprocessor).process(error, error.getEntity(), context); Mockito.verify(conn).sendResponseHeader(error); Mockito.verify(conn).sendResponseEntity(error); Mockito.verify(conn).close(); } @Test void testUnsupportedHttpVersionException() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest("whatever", "/"); Mockito.when(conn.receiveRequestHeader()).thenReturn(request); Mockito.when(responseFactory.newHttpResponse(200)).thenReturn(response); Mockito.when(handlerResolver.resolve(request, context)).thenReturn(requestHandler); Mockito.doThrow(new UnsupportedHttpVersionException()).when( requestHandler).handle(request, response, context); httpservice.handleRequest(conn, context); final ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(ClassicHttpResponse.class); Mockito.verify(conn).sendResponseHeader(responseCaptor.capture()); final ClassicHttpResponse error = responseCaptor.getValue(); Assertions.assertNotNull(error); Assertions.assertSame(request, context.getRequest()); Assertions.assertEquals(HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED, error.getCode()); Mockito.verify(httprocessor).process(error, error.getEntity(), context); Mockito.verify(conn).sendResponseHeader(error); Mockito.verify(conn).sendResponseEntity(error); Mockito.verify(conn).close(); } @Test void testProtocolException() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest("whatever", "/"); Mockito.when(conn.receiveRequestHeader()).thenReturn(request); Mockito.when(responseFactory.newHttpResponse(200)).thenReturn(response); Mockito.when(handlerResolver.resolve(request, context)).thenReturn(requestHandler); Mockito.doThrow(new ProtocolException("oh, this world is wrong")).when( requestHandler).handle(request, response, context); httpservice.handleRequest(conn, context); final ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(ClassicHttpResponse.class); Mockito.verify(conn).sendResponseHeader(responseCaptor.capture()); final ClassicHttpResponse error = responseCaptor.getValue(); Assertions.assertNotNull(error); Assertions.assertSame(request, context.getRequest()); Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, error.getCode()); Mockito.verify(httprocessor).process(error, error.getEntity(), context); Mockito.verify(conn).sendResponseHeader(error); Mockito.verify(conn).sendResponseEntity(error); Mockito.verify(conn).close(); } @Test void testConnectionKeepAlive() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); Mockito.when(conn.receiveRequestHeader()).thenReturn(request); Mockito.when(responseFactory.newHttpResponse(200)).thenReturn(response); Mockito.when(handlerResolver.resolve(request, context)).thenReturn(requestHandler); Mockito.when(connReuseStrategy.keepAlive(request, response, context)).thenReturn(Boolean.TRUE); httpservice.handleRequest(conn, context); Assertions.assertEquals(HttpStatus.SC_OK, response.getCode()); Assertions.assertSame(request, context.getRequest()); Assertions.assertSame(response, context.getResponse()); Mockito.verify(httprocessor).process(request, request.getEntity(), context); Mockito.verify(httprocessor).process(response, response.getEntity(), context); Mockito.verify(conn).sendResponseHeader(response); Mockito.verify(conn).sendResponseEntity(response); Mockito.verify(conn).flush(); Mockito.verify(conn, Mockito.never()).close(); Mockito.verify(response).close(); } @Test void testNoContentResponse() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); Mockito.when(conn.receiveRequestHeader()).thenReturn(request); Mockito.when(responseFactory.newHttpResponse(200)).thenReturn(response); Mockito.when(handlerResolver.resolve(request, context)).thenReturn((request1, response, context1) -> response.setCode(HttpStatus.SC_NO_CONTENT)); Mockito.when(connReuseStrategy.keepAlive(request, response, context)).thenReturn(Boolean.TRUE); httpservice.handleRequest(conn, context); Assertions.assertSame(request, context.getRequest()); Mockito.verify(httprocessor).process(response, response.getEntity(), context); Mockito.verify(conn).sendResponseHeader(response); Mockito.verify(conn, Mockito.never()).sendResponseEntity(ArgumentMatchers.any()); Mockito.verify(conn).flush(); Mockito.verify(conn, Mockito.never()).close(); Mockito.verify(response).close(); } @Test void testResponseToHead() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.HEAD, "/"); Mockito.when(conn.receiveRequestHeader()).thenReturn(request); Mockito.when(responseFactory.newHttpResponse(200)).thenReturn(response); Mockito.when(handlerResolver.resolve(request, context)).thenReturn(requestHandler); Mockito.when(connReuseStrategy.keepAlive(request, response, context)).thenReturn(Boolean.TRUE); httpservice.handleRequest(conn, context); Assertions.assertSame(request, context.getRequest()); Mockito.verify(httprocessor).process(response, response.getEntity(), context); Mockito.verify(requestHandler).handle(request, response, context); Mockito.verify(conn).sendResponseHeader(response); Mockito.verify(conn, Mockito.never()).sendResponseEntity(ArgumentMatchers.any()); Mockito.verify(conn).flush(); Mockito.verify(conn, Mockito.never()).close(); Mockito.verify(response).close(); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestIdentityInputStream.java0100644 0000000 0000000 00000010257 00000250600 027632 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.ByteArrayInputStream; import org.apache.hc.core5.http.StreamClosedException; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.io.SessionInputBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link IdentityInputStream}. */ class TestIdentityInputStream { @Test void testBasicRead() throws Exception { final byte[] input = new byte[] {'a', 'b', 'c'}; final ByteArrayInputStream inputStream = new ByteArrayInputStream(input); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); try (IdentityInputStream in = new IdentityInputStream(inBuffer, inputStream)) { final byte[] tmp = new byte[2]; Assertions.assertEquals(2, in.read(tmp, 0, tmp.length)); Assertions.assertEquals('a', tmp[0]); Assertions.assertEquals('b', tmp[1]); Assertions.assertEquals('c', in.read()); Assertions.assertEquals(-1, in.read(tmp, 0, tmp.length)); Assertions.assertEquals(-1, in.read()); Assertions.assertEquals(-1, in.read(tmp, 0, tmp.length)); Assertions.assertEquals(-1, in.read()); } } @Test void testClosedCondition() throws Exception { final byte[] input = new byte[] {'a', 'b', 'c'}; final ByteArrayInputStream inputStream = new ByteArrayInputStream(input); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final IdentityInputStream in = new IdentityInputStream(inBuffer, inputStream); in.close(); in.close(); Assertions.assertEquals(0, in.available()); final byte[] tmp = new byte[2]; Assertions.assertThrows(StreamClosedException.class, () -> in.read(tmp, 0, tmp.length)); Assertions.assertThrows(StreamClosedException.class, () -> in.read()); } @Test void testAvailable() throws Exception { final byte[] input = new byte[] {'a', 'b', 'c'}; final ByteArrayInputStream inputStream = new ByteArrayInputStream(input); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(new BasicHttpTransportMetrics(), 16, 16, 1024, null); try (IdentityInputStream in = new IdentityInputStream(inBuffer, inputStream)) { in.read(); Assertions.assertEquals(2, in.available()); } } @Test void testAvailableInStream() throws Exception { final byte[] input = new byte[] {'a', 'b', 'c', 'd', 'e', 'f'}; final ByteArrayInputStream inputStream = new ByteArrayInputStream(input); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(new BasicHttpTransportMetrics(), 16, 0, 1024, null); try (IdentityInputStream in = new IdentityInputStream(inBuffer, inputStream)) { final byte[] tmp = new byte[3]; Assertions.assertEquals(3, in.read(tmp)); Assertions.assertEquals(3, in.available()); } } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestIdentityOutputStream.java0100644 0000000 0000000 00000007766 00000250600 030046 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import org.apache.hc.core5.http.StreamClosedException; import org.apache.hc.core5.http.io.SessionOutputBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link IdentityOutputStream}. */ class TestIdentityOutputStream { @Test void testBasics() throws Exception { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); try (OutputStream out = new IdentityOutputStream(outbuffer, outputStream)) { final byte[] tmp = new byte[10]; out.write(tmp, 0, 10); out.write(tmp); out.write(1); out.flush(); } final byte[] data = outputStream.toByteArray(); Assertions.assertEquals(21, data.length); } @Test void testClose() throws Exception { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final OutputStream out = new IdentityOutputStream(outbuffer, outputStream); out.close(); out.close(); final byte[] tmp = new byte[10]; Assertions.assertThrows(IOException.class, () -> out.write(tmp)); Assertions.assertThrows(IOException.class, () -> out.write(1)); } @Test void testBasicWrite() throws Exception { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); try (OutputStream out = new IdentityOutputStream(outbuffer, outputStream)) { out.write(new byte[] { 'a', 'b' }, 0, 2); out.write('c'); out.flush(); final byte[] input = outputStream.toByteArray(); Assertions.assertNotNull(input); final byte[] expected = new byte[] { 'a', 'b', 'c' }; Assertions.assertEquals(expected.length, input.length); for (int i = 0; i < expected.length; i++) { Assertions.assertEquals(expected[i], input[i]); } } } @Test void testClosedCondition() throws Exception { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final OutputStream out = new IdentityOutputStream(outbuffer, outputStream); out.close(); out.close(); final byte[] tmp = new byte[2]; Assertions.assertThrows(StreamClosedException.class, () -> out.write(tmp, 0, tmp.length)); Assertions.assertThrows(StreamClosedException.class, () -> out.write('a')); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestMessageParser.java0100644 0000000 0000000 00000015742 00000250600 026412 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.io.SessionInputBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link AbstractMessageParser}. */ class TestMessageParser { @Test void testBasicHeaderParsing() throws Exception { final String s = "header1: stuff\r\n" + "header2: stuff \r\n" + "header3: stuff\r\n" + " and more stuff\r\n" + "\t and even more stuff\r\n" + " \r\n" + "\r\n"; final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.US_ASCII.newDecoder()); final Header[] headers = AbstractMessageParser.parseHeaders(inBuffer, inputStream, -1, -1, null); Assertions.assertNotNull(headers); Assertions.assertEquals(3, headers.length); Assertions.assertEquals("header1", headers[0].getName()); Assertions.assertEquals("stuff", headers[0].getValue()); Assertions.assertEquals("header2", headers[1].getName()); Assertions.assertEquals("stuff", headers[1].getValue()); Assertions.assertEquals("header3", headers[2].getName()); Assertions.assertEquals("stuff and more stuff and even more stuff", headers[2].getValue()); } @Test void testParsingHeader() throws Exception { final String s = "header1: stuff; param1 = value1; param2 = \"value 2\" \r\n" + "\r\n"; final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.US_ASCII.newDecoder()); final Header[] headers = AbstractMessageParser.parseHeaders(inBuffer, inputStream, -1, -1, null); Assertions.assertNotNull(headers); Assertions.assertEquals(1, headers.length); Assertions.assertEquals("header1: stuff; param1 = value1; param2 = \"value 2\" ", headers[0].toString()); } @Test void testParsingInvalidHeaders() { final String s1 = " stuff\r\n" + "header1: stuff\r\n" + "\r\n"; final ByteArrayInputStream inputStream1 = new ByteArrayInputStream(s1.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer1 = new SessionInputBufferImpl(16, StandardCharsets.US_ASCII.newDecoder()); Assertions.assertThrows(ProtocolException.class, () -> AbstractMessageParser.parseHeaders(inBuffer1, inputStream1, -1, -1, null)); final String s2 = " : stuff\r\n" + "header1: stuff\r\n" + "\r\n"; final ByteArrayInputStream inputStream2 = new ByteArrayInputStream(s2.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer2 = new SessionInputBufferImpl(16, StandardCharsets.US_ASCII.newDecoder()); Assertions.assertThrows(ProtocolException.class, () -> AbstractMessageParser.parseHeaders(inBuffer2, inputStream2, -1, -1, null)); } @Test void testParsingMalformedFirstHeader() throws Exception { final String s = " header1: stuff\r\n" + "header2: stuff \r\n"; final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.US_ASCII.newDecoder()); final Header[] headers = AbstractMessageParser.parseHeaders(inBuffer, inputStream, -1, -1, null); Assertions.assertNotNull(headers); Assertions.assertEquals(2, headers.length); Assertions.assertEquals("header1", headers[0].getName()); Assertions.assertEquals("stuff", headers[0].getValue()); Assertions.assertEquals("header2", headers[1].getName()); Assertions.assertEquals("stuff", headers[1].getValue()); } @Test void testEmptyDataStream() throws Exception { final String s = ""; final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.US_ASCII.newDecoder()); final Header[] headers = AbstractMessageParser.parseHeaders(inBuffer, inputStream, -1, -1, null); Assertions.assertNotNull(headers); Assertions.assertEquals(0, headers.length); } @Test void testMaxHeaderCount() { final String s = "header1: stuff\r\n" + "header2: stuff \r\n" + "header3: stuff\r\n" + "\r\n"; final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.US_ASCII.newDecoder()); Assertions.assertThrows(IOException.class, () -> AbstractMessageParser.parseHeaders(inBuffer, inputStream, 2, -1, null)); } @Test void testMaxHeaderCountForFoldedHeader() { final String s = "header1: stuff\r\n" + " stuff \r\n" + " stuff\r\n" + "\r\n"; final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.US_ASCII.newDecoder()); Assertions.assertThrows(IOException.class, () -> AbstractMessageParser.parseHeaders(inBuffer, inputStream, 2, 15, null)); } } ././@LongLink0100644 0000000 0000000 00000000147 00000250600 011613 Lustar 0000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestMonitoringResponseOutOfOrderStrategy.javahttpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestMonitoringResponseOutOfOrderStrategy.ja0100644 0000000 0000000 00000011746 00000250600 032662 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.io.InputStream; import javax.net.ssl.SSLSession; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.io.HttpClientConnection; import org.apache.hc.core5.http.io.ResponseOutOfOrderStrategy; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; class TestMonitoringResponseOutOfOrderStrategy { private static final ClassicHttpRequest REQUEST = new BasicClassicHttpRequest("POST", "/path"); @Test void testFirstByteIsNotCheckedSsl() throws IOException { final boolean earlyResponse = MonitoringResponseOutOfOrderStrategy.INSTANCE.isEarlyResponseDetected( REQUEST, connection(true, true), // SSLSocket streams report zero bytes available socketInputStream(0), 0, 1); Assertions.assertFalse(earlyResponse); } @Test void testFirstByteIsNotCheckedPlain() throws IOException { final boolean earlyResponse = MonitoringResponseOutOfOrderStrategy.INSTANCE.isEarlyResponseDetected( REQUEST, connection(true, false), socketInputStream(1), 0, 1); Assertions.assertFalse(earlyResponse); } @Test void testWritesWithinChunkAreNotChecked() throws IOException { final boolean earlyResponse = MonitoringResponseOutOfOrderStrategy.INSTANCE.isEarlyResponseDetected( REQUEST, connection(true, true), socketInputStream(0), 1, 8190); Assertions.assertFalse(earlyResponse, "There is data available, but checks shouldn't occur until just prior to the 8192nd byte"); } @Test void testWritesAcrossChunksAreChecked() throws IOException { final boolean earlyResponse = MonitoringResponseOutOfOrderStrategy.INSTANCE.isEarlyResponseDetected( REQUEST, connection(true, true), socketInputStream(0), 8191, 1); Assertions.assertTrue(earlyResponse); } @Test void testMaximumChunks() throws IOException { final ResponseOutOfOrderStrategy strategy = new MonitoringResponseOutOfOrderStrategy(1, 2); Assertions.assertTrue(strategy.isEarlyResponseDetected( REQUEST, connection(true, true), socketInputStream(0), 0, 1)); Assertions.assertTrue(strategy.isEarlyResponseDetected( REQUEST, connection(true, true), socketInputStream(0), 1, 2)); Assertions.assertFalse(strategy.isEarlyResponseDetected( REQUEST, connection(true, true), socketInputStream(0), 2, 3)); } private static InputStream socketInputStream(final int available) throws IOException { final InputStream stream = Mockito.mock(InputStream.class); Mockito.when(stream.available()).thenReturn(available); return stream; } private static HttpClientConnection connection(final boolean dataAvailable, final boolean ssl) throws IOException { final HttpClientConnection connection = Mockito.mock(HttpClientConnection.class); Mockito.when(connection.isDataAvailable(ArgumentMatchers.any(Timeout.class))).thenReturn(dataAvailable); if (ssl) { Mockito.when(connection.getSSLSession()).thenReturn(Mockito.mock(SSLSession.class)); } return connection; } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestRequestParser.java0100644 0000000 0000000 00000014103 00000250600 026444 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.ByteArrayInputStream; import java.io.InterruptedIOException; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.RequestHeaderFieldsTooLargeException; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.io.SessionInputBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link DefaultHttpRequestParser}. */ class TestRequestParser { @Test void testBasicMessageParsing() throws Exception { final String s = "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "User-Agent: whatever\r\n" + "Cookie: c1=stuff\r\n" + "\r\n"; final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.US_ASCII.newDecoder()); final DefaultHttpRequestParser parser = new DefaultHttpRequestParser(); final ClassicHttpRequest httprequest = parser.parse(inBuffer, inputStream); Assertions.assertEquals(Method.GET.name(), httprequest.getMethod()); Assertions.assertEquals("/", httprequest.getPath()); final Header[] headers = httprequest.getHeaders(); Assertions.assertEquals(3, headers.length); } @Test void testConnectionClosedException() throws Exception { final ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[] {}); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final DefaultHttpRequestParser parser = new DefaultHttpRequestParser(); final ClassicHttpRequest request = parser.parse(inBuffer, inputStream); Assertions.assertNull(request); } @Test void testBasicMessageParsingLeadingEmptyLines() throws Exception { final String s = "\r\n" + "\r\n" + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "\r\n"; final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.US_ASCII.newDecoder()); final DefaultHttpRequestParser parser = new DefaultHttpRequestParser( Http1Config.custom().setMaxEmptyLineCount(3).build()); final ClassicHttpRequest httprequest = parser.parse(inBuffer, inputStream); Assertions.assertEquals(Method.GET.name(), httprequest.getMethod()); Assertions.assertEquals("/", httprequest.getPath()); final Header[] headers = httprequest.getHeaders(); Assertions.assertEquals(1, headers.length); } @Test void testBasicMessageParsingTooManyLeadingEmptyLines() { final String s = "\r\n" + "\r\n" + "\r\n" + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "\r\n"; final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.US_ASCII.newDecoder()); final DefaultHttpRequestParser parser = new DefaultHttpRequestParser( Http1Config.custom().setMaxEmptyLineCount(3).build()); Assertions.assertThrows(RequestHeaderFieldsTooLargeException.class, () -> parser.parse(inBuffer, inputStream)); } @Test void testMessageParsingTimeout() throws Exception { final String s = "GET \000/ HTTP/1.1\r\000\n" + "Host: loca\000lhost\r\n" + "User-Agent: whatever\r\n" + "Coo\000kie: c1=stuff\r\n" + "\000\r\n"; final TimeoutByteArrayInputStream inputStream = new TimeoutByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final DefaultHttpRequestParser parser = new DefaultHttpRequestParser(); int timeoutCount = 0; ClassicHttpRequest httprequest = null; for (int i = 0; i < 10; i++) { try { httprequest = parser.parse(inBuffer, inputStream); break; } catch (final InterruptedIOException ex) { timeoutCount++; } } Assertions.assertNotNull(httprequest); Assertions.assertEquals(5, timeoutCount); Assertions.assertEquals(Method.GET.name(), httprequest.getMethod()); Assertions.assertEquals("/", httprequest.getPath()); final Header[] headers = httprequest.getHeaders(); Assertions.assertEquals(3, headers.length); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestResponseParser.java0100644 0000000 0000000 00000014031 00000250600 026612 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.ByteArrayInputStream; import java.io.InterruptedIOException; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.MessageConstraintException; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.io.SessionInputBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link DefaultHttpResponseParser}. */ class TestResponseParser { @Test void testBasicMessageParsing() throws Exception { final String s = "HTTP/1.1 200 OK\r\n" + "Server: whatever\r\n" + "Date: some date\r\n" + "Set-Cookie: c1=stuff\r\n" + "\r\n"; final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.US_ASCII.newDecoder()); final DefaultHttpResponseParser parser = new DefaultHttpResponseParser(); final ClassicHttpResponse httpresponse = parser.parse(inBuffer, inputStream); Assertions.assertEquals(200, httpresponse.getCode()); Assertions.assertEquals("OK", httpresponse.getReasonPhrase()); final Header[] headers = httpresponse.getHeaders(); Assertions.assertEquals(3, headers.length); } @Test void testConnectionClosedException() throws Exception { final ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[] {}); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final DefaultHttpResponseParser parser = new DefaultHttpResponseParser(); final ClassicHttpResponse response = parser.parse(inBuffer, inputStream); Assertions.assertNull(response); } @Test void testBasicMessageParsingLeadingEmptyLines() throws Exception { final String s = "\r\n" + "\r\n" + "HTTP/1.1 200 OK\r\n" + "Server: whatever\r\n" + "\r\n"; final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.US_ASCII.newDecoder()); final DefaultHttpResponseParser parser = new DefaultHttpResponseParser( Http1Config.custom().setMaxEmptyLineCount(3).build()); final ClassicHttpResponse httpresponse = parser.parse(inBuffer, inputStream); Assertions.assertEquals(200, httpresponse.getCode()); Assertions.assertEquals("OK", httpresponse.getReasonPhrase()); final Header[] headers = httpresponse.getHeaders(); Assertions.assertEquals(1, headers.length); } @Test void testBasicMessageParsingTooManyLeadingEmptyLines() { final String s = "\r\n" + "\r\n" + "\r\n" + "HTTP/1.1 200 OK\r\n" + "Server: whatever\r\n" + "\r\n"; final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.US_ASCII.newDecoder()); final DefaultHttpResponseParser parser = new DefaultHttpResponseParser( Http1Config.custom().setMaxEmptyLineCount(3).build()); Assertions.assertThrows(MessageConstraintException.class, () -> parser.parse(inBuffer, inputStream)); } @Test void testMessageParsingTimeout() throws Exception { final String s = "HTTP\000/1.1 200\000 OK\r\n" + "Server: wha\000tever\r\n" + "Date: some date\r\n" + "Set-Coo\000kie: c1=stuff\r\n" + "\000\r\n"; final TimeoutByteArrayInputStream inputStream = new TimeoutByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final DefaultHttpResponseParser parser = new DefaultHttpResponseParser(); int timeoutCount = 0; ClassicHttpResponse httpresponse = null; for (int i = 0; i < 10; i++) { try { httpresponse = parser.parse(inBuffer, inputStream); break; } catch (final InterruptedIOException ex) { timeoutCount++; } } Assertions.assertNotNull(httpresponse); Assertions.assertEquals(5, timeoutCount); Assertions.assertEquals(200, httpresponse.getCode()); Assertions.assertEquals("OK", httpresponse.getReasonPhrase()); final Header[] headers = httpresponse.getHeaders(); Assertions.assertEquals(3, headers.length); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestSessionInOutBuffers.java0100644 0000000 0000000 00000074766 00000250600 027603 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.MessageConstraintException; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.io.HttpTransportMetrics; import org.apache.hc.core5.http.io.SessionInputBuffer; import org.apache.hc.core5.http.io.SessionOutputBuffer; import org.apache.hc.core5.util.CharArrayBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; class TestSessionInOutBuffers { @Test void testBasicBufferProperties() throws Exception { final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[] { 1, 2 , 3}); Assertions.assertEquals(16, inBuffer.capacity()); Assertions.assertEquals(16, inBuffer.available()); Assertions.assertEquals(0, inBuffer.length()); inBuffer.read(inputStream); Assertions.assertEquals(14, inBuffer.available()); Assertions.assertEquals(2, inBuffer.length()); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); Assertions.assertEquals(16, outbuffer.capacity()); Assertions.assertEquals(16, outbuffer.available()); Assertions.assertEquals(0, outbuffer.length()); outbuffer.write(new byte[] {1, 2, 3}, outputStream); Assertions.assertEquals(13, outbuffer.available()); Assertions.assertEquals(3, outbuffer.length()); } @Test void testBasicReadWriteLine() throws Exception { final String[] teststrs = new String[5]; teststrs[0] = "Hello"; teststrs[1] = "This string should be much longer than the size of the output buffer " + "which is only 16 bytes for this test"; final StringBuilder buffer = new StringBuilder(); for (int i = 0; i < 15; i++) { buffer.append("123456789 "); } buffer.append("and stuff like that"); teststrs[2] = buffer.toString(); teststrs[3] = ""; teststrs[4] = "And goodbye"; final CharArrayBuffer chbuffer = new CharArrayBuffer(16); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (final String teststr : teststrs) { chbuffer.clear(); chbuffer.append(teststr); outbuffer.writeLine(chbuffer, outputStream); } //these write operations should have no effect outbuffer.writeLine(null, outputStream); outbuffer.flush(outputStream); HttpTransportMetrics tmetrics = outbuffer.getMetrics(); final long bytesWritten = tmetrics.getBytesTransferred(); long expected = 0; for (final String teststr : teststrs) { expected += teststr.length() + 2/*CRLF*/; } Assertions.assertEquals(expected, bytesWritten); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); for (final String teststr : teststrs) { chbuffer.clear(); inBuffer.readLine(chbuffer, inputStream); Assertions.assertEquals(teststr, chbuffer.toString()); } chbuffer.clear(); Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream)); chbuffer.clear(); Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream)); tmetrics = inBuffer.getMetrics(); final long bytesRead = tmetrics.getBytesTransferred(); Assertions.assertEquals(expected, bytesRead); } @Test void testComplexReadWriteLine() throws Exception { final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); outbuffer.write(new byte[] {'a', '\n'}, outputStream); outbuffer.write(new byte[] {'\r', '\n'}, outputStream); outbuffer.write(new byte[] {'\r', '\r', '\n'}, outputStream); outbuffer.write(new byte[] {'\n'},outputStream); //these write operations should have no effect outbuffer.write(null, outputStream); outbuffer.write(null, 0, 12, outputStream); outbuffer.flush(outputStream); long bytesWritten = outbuffer.getMetrics().getBytesTransferred(); Assertions.assertEquals(8, bytesWritten); final StringBuilder buffer = new StringBuilder(); for (int i = 0; i < 14; i++) { buffer.append("a"); } final String s1 = buffer.toString(); buffer.append("\r\n"); outbuffer.write(buffer.toString().getBytes(StandardCharsets.US_ASCII), outputStream); outbuffer.flush(outputStream); bytesWritten = outbuffer.getMetrics().getBytesTransferred(); Assertions.assertEquals(8 + 14 + 2, bytesWritten); buffer.setLength(0); for (int i = 0; i < 15; i++) { buffer.append("a"); } final String s2 = buffer.toString(); buffer.append("\r\n"); outbuffer.write(buffer.toString().getBytes(StandardCharsets.US_ASCII), outputStream); outbuffer.flush(outputStream); bytesWritten = outbuffer.getMetrics().getBytesTransferred(); Assertions.assertEquals(8 + 14 + 2 + 15 + 2 , bytesWritten); buffer.setLength(0); for (int i = 0; i < 16; i++) { buffer.append("a"); } final String s3 = buffer.toString(); buffer.append("\r\n"); outbuffer.write(buffer.toString().getBytes(StandardCharsets.US_ASCII), outputStream); outbuffer.flush(outputStream); bytesWritten = outbuffer.getMetrics().getBytesTransferred(); Assertions.assertEquals(8 + 14 + 2 + 15 + 2 + 16 + 2, bytesWritten); outbuffer.write(new byte[] {'a'}, outputStream); outbuffer.flush(outputStream); bytesWritten = outbuffer.getMetrics().getBytesTransferred(); Assertions.assertEquals(8 + 14 + 2 + 15 + 2 + 16 + 2 + 1, bytesWritten); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); chbuffer.clear(); inBuffer.readLine(chbuffer, inputStream); Assertions.assertEquals("a", chbuffer.toString()); chbuffer.clear(); inBuffer.readLine(chbuffer, inputStream); Assertions.assertEquals("", chbuffer.toString()); chbuffer.clear(); inBuffer.readLine(chbuffer, inputStream); Assertions.assertEquals("\r", chbuffer.toString()); chbuffer.clear(); inBuffer.readLine(chbuffer, inputStream); Assertions.assertEquals("", chbuffer.toString()); chbuffer.clear(); inBuffer.readLine(chbuffer, inputStream); Assertions.assertEquals(s1, chbuffer.toString()); chbuffer.clear(); inBuffer.readLine(chbuffer, inputStream); Assertions.assertEquals(s2, chbuffer.toString()); chbuffer.clear(); inBuffer.readLine(chbuffer, inputStream); Assertions.assertEquals(s3, chbuffer.toString()); chbuffer.clear(); inBuffer.readLine(chbuffer, inputStream); Assertions.assertEquals("a", chbuffer.toString()); chbuffer.clear(); inBuffer.readLine(chbuffer, inputStream); Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream)); chbuffer.clear(); inBuffer.readLine(chbuffer, inputStream); Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream)); final long bytesRead = inBuffer.getMetrics().getBytesTransferred(); Assertions.assertEquals(bytesWritten, bytesRead); } @Test void testBasicReadWriteLineLargeBuffer() throws Exception { final String[] teststrs = new String[5]; teststrs[0] = "Hello"; teststrs[1] = "This string should be much longer than the size of the output buffer " + "which is only 16 bytes for this test"; final StringBuilder buffer = new StringBuilder(); for (int i = 0; i < 15; i++) { buffer.append("123456789 "); } buffer.append("and stuff like that"); teststrs[2] = buffer.toString(); teststrs[3] = ""; teststrs[4] = "And goodbye"; final CharArrayBuffer chbuffer = new CharArrayBuffer(16); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (final String teststr : teststrs) { chbuffer.clear(); chbuffer.append(teststr); outbuffer.writeLine(chbuffer, outputStream); } //these write operations should have no effect outbuffer.writeLine(null, outputStream); outbuffer.flush(outputStream); final long bytesWritten = outbuffer.getMetrics().getBytesTransferred(); long expected = 0; for (final String teststr : teststrs) { expected += teststr.length() + 2/*CRLF*/; } Assertions.assertEquals(expected, bytesWritten); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(1024); final ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); for (final String teststr : teststrs) { chbuffer.clear(); inBuffer.readLine(chbuffer, inputStream); Assertions.assertEquals(teststr, chbuffer.toString()); } chbuffer.clear(); Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream)); chbuffer.clear(); Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream)); final long bytesRead = inBuffer.getMetrics().getBytesTransferred(); Assertions.assertEquals(expected, bytesRead); } @Test void testReadWriteBytes() throws Exception { // make the buffer larger than that of outbuffer final byte[] out = new byte[40]; for (int i = 0; i < out.length; i++) { out[i] = (byte)('0' + i); } final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); int off = 0; int remaining = out.length; while (remaining > 0) { int chunk = 10; if (chunk > remaining) { chunk = remaining; } outbuffer.write(out, off, chunk, outputStream); off += chunk; remaining -= chunk; } outbuffer.flush(outputStream); final long bytesWritten = outbuffer.getMetrics().getBytesTransferred(); Assertions.assertEquals(out.length, bytesWritten); final byte[] tmp = outputStream.toByteArray(); Assertions.assertEquals(out.length, tmp.length); for (int i = 0; i < out.length; i++) { Assertions.assertEquals(out[i], tmp[i]); } final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(tmp); // these read operations will have no effect Assertions.assertEquals(0, inBuffer.read(null, 0, 10, inputStream)); Assertions.assertEquals(0, inBuffer.read(null, inputStream)); long bytesRead = inBuffer.getMetrics().getBytesTransferred(); Assertions.assertEquals(0, bytesRead); final byte[] in = new byte[40]; off = 0; remaining = in.length; while (remaining > 0) { int chunk = 10; if (chunk > remaining) { chunk = remaining; } final int readLen = inBuffer.read(in, off, chunk, inputStream); if (readLen == -1) { break; } off += readLen; remaining -= readLen; } for (int i = 0; i < out.length; i++) { Assertions.assertEquals(out[i], in[i]); } Assertions.assertEquals(-1, inBuffer.read(tmp, inputStream)); Assertions.assertEquals(-1, inBuffer.read(tmp, inputStream)); bytesRead = inBuffer.getMetrics().getBytesTransferred(); Assertions.assertEquals(out.length, bytesRead); } @Test void testReadWriteByte() throws Exception { // make the buffer larger than that of outbuffer final byte[] out = new byte[40]; for (int i = 0; i < out.length; i++) { out[i] = (byte)(120 + i); } final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (final byte element : out) { outbuffer.write(element, outputStream); } outbuffer.flush(outputStream); final long bytesWritten = outbuffer.getMetrics().getBytesTransferred(); Assertions.assertEquals(out.length, bytesWritten); final byte[] tmp = outputStream.toByteArray(); Assertions.assertEquals(out.length, tmp.length); for (int i = 0; i < out.length; i++) { Assertions.assertEquals(out[i], tmp[i]); } final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream(tmp); final byte[] in = new byte[40]; for (int i = 0; i < in.length; i++) { in[i] = (byte)inBuffer.read(inputStream); } for (int i = 0; i < out.length; i++) { Assertions.assertEquals(out[i], in[i]); } Assertions.assertEquals(-1, inBuffer.read(inputStream)); Assertions.assertEquals(-1, inBuffer.read(inputStream)); final long bytesRead = inBuffer.getMetrics().getBytesTransferred(); Assertions.assertEquals(out.length, bytesRead); } @Test void testWriteSmallFragmentBuffering() throws Exception { final OutputStream outputStream = Mockito.mock(OutputStream.class); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(new BasicHttpTransportMetrics(), 16, 16, null); outbuffer.write(1, outputStream); outbuffer.write(2, outputStream); outbuffer.write(new byte[] {1, 2}, outputStream); outbuffer.write(new byte[]{3, 4}, outputStream); outbuffer.flush(outputStream); Mockito.verify(outputStream, Mockito.times(1)).write(ArgumentMatchers.any(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt()); Mockito.verify(outputStream, Mockito.never()).write(ArgumentMatchers.anyInt()); } @Test void testWriteSmallFragmentNoBuffering() throws Exception { final OutputStream outputStream = Mockito.mock(OutputStream.class); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(new BasicHttpTransportMetrics(), 16, 0, null); outbuffer.write(1, outputStream); outbuffer.write(2, outputStream); outbuffer.write(new byte[] {1, 2}, outputStream); outbuffer.write(new byte[]{3, 4}, outputStream); Mockito.verify(outputStream, Mockito.times(2)).write(ArgumentMatchers.any(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt()); Mockito.verify(outputStream, Mockito.times(2)).write(ArgumentMatchers.anyInt()); } @Test void testLineLimit() throws Exception { final String s = "a very looooooooooooooooooooooooooooooooooooooooooong line\r\n"; final byte[] tmp = s.getBytes(StandardCharsets.US_ASCII); // no limit final SessionInputBuffer inBuffer1 = new SessionInputBufferImpl(5); final InputStream inputStream1 = new ByteArrayInputStream(tmp); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); inBuffer1.readLine(chbuffer, inputStream1); final long bytesRead = inBuffer1.getMetrics().getBytesTransferred(); Assertions.assertEquals(60, bytesRead); // 15 char limit final SessionInputBuffer inBuffer2 = new SessionInputBufferImpl(5, 15); final InputStream inputStream2 = new ByteArrayInputStream(tmp); chbuffer.clear(); Assertions.assertThrows(MessageConstraintException.class, () -> inBuffer2.readLine(chbuffer, inputStream2)); } @Test void testLineLimit2() throws Exception { final String s = "just a line\r\n"; final byte[] tmp = s.getBytes(StandardCharsets.US_ASCII); // no limit final SessionInputBuffer inBuffer1 = new SessionInputBufferImpl(25); final InputStream inputStream1 = new ByteArrayInputStream(tmp); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); inBuffer1.readLine(chbuffer, inputStream1); final long bytesRead = inBuffer1.getMetrics().getBytesTransferred(); Assertions.assertEquals(13, bytesRead); // 10 char limit final SessionInputBuffer inBuffer2 = new SessionInputBufferImpl(25, 10); final InputStream inputStream2 = new ByteArrayInputStream(tmp); chbuffer.clear(); Assertions.assertThrows(MessageConstraintException.class, () -> inBuffer2.readLine(chbuffer, inputStream2)); } @Test //HTTPCORE-472 void testLineLimit3() throws Exception { final String s = "012345678\r\nblaaaaaaaaaaaaaaaaaah"; final byte[] tmp = s.getBytes(StandardCharsets.US_ASCII); final SessionInputBuffer inBuffer1 = new SessionInputBufferImpl(128); final ByteArrayInputStream inputStream = new ByteArrayInputStream(tmp); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); inBuffer1.readLine(chbuffer, inputStream); Assertions.assertEquals("012345678", chbuffer.toString()); } @Test void testReadLineFringeCase1() throws Exception { final String s = "abc\r\n"; final byte[] tmp = s.getBytes(StandardCharsets.US_ASCII); final SessionInputBuffer inBuffer1 = new SessionInputBufferImpl(128); final ByteArrayInputStream inputStream = new ByteArrayInputStream(tmp); Assertions.assertEquals('a', inBuffer1.read(inputStream)); Assertions.assertEquals('b', inBuffer1.read(inputStream)); Assertions.assertEquals('c', inBuffer1.read(inputStream)); Assertions.assertEquals('\r', inBuffer1.read(inputStream)); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); Assertions.assertEquals(0, inBuffer1.readLine(chbuffer, inputStream)); } static final int[] SWISS_GERMAN_HELLO = { 0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4 }; static final int[] RUSSIAN_HELLO = { 0x412, 0x441, 0x435, 0x43C, 0x5F, 0x43F, 0x440, 0x438, 0x432, 0x435, 0x442 }; private static String constructString(final int [] unicodeChars) { final StringBuilder buffer = new StringBuilder(); if (unicodeChars != null) { for (final int unicodeChar : unicodeChars) { buffer.append((char)unicodeChar); } } return buffer.toString(); } @Test void testMultibyteCodedReadWriteLine() throws Exception { final String s1 = constructString(SWISS_GERMAN_HELLO); final String s2 = constructString(RUSSIAN_HELLO); final String s3 = "Like hello and stuff"; final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16, StandardCharsets.UTF_8.newEncoder()); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); for (int i = 0; i < 10; i++) { chbuffer.clear(); chbuffer.append(s1); outbuffer.writeLine(chbuffer, outputStream); chbuffer.clear(); chbuffer.append(s2); outbuffer.writeLine(chbuffer, outputStream); chbuffer.clear(); chbuffer.append(s3); outbuffer.writeLine(chbuffer, outputStream); } outbuffer.flush(outputStream); final long bytesWritten = outbuffer.getMetrics().getBytesTransferred(); final long expected = ((s1.getBytes(StandardCharsets.UTF_8).length + 2) + (s2.getBytes(StandardCharsets.UTF_8).length + 2) + (s3.getBytes(StandardCharsets.UTF_8).length + 2)) * 10L; Assertions.assertEquals(expected, bytesWritten); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.UTF_8.newDecoder()); final ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); for (int i = 0; i < 10; i++) { chbuffer.clear(); inBuffer.readLine(chbuffer, inputStream); Assertions.assertEquals(s1, chbuffer.toString()); chbuffer.clear(); inBuffer.readLine(chbuffer, inputStream); Assertions.assertEquals(s2, chbuffer.toString()); chbuffer.clear(); inBuffer.readLine(chbuffer, inputStream); Assertions.assertEquals(s3, chbuffer.toString()); } chbuffer.clear(); Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream)); chbuffer.clear(); Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream)); final long bytesRead = inBuffer.getMetrics().getBytesTransferred(); Assertions.assertEquals(expected, bytesRead); } @Test void testMultibyteCodedReadWriteLongLine() throws Exception { final String s1 = constructString(SWISS_GERMAN_HELLO); final String s2 = constructString(RUSSIAN_HELLO); final String s3 = "Like hello and stuff"; final StringBuilder buf = new StringBuilder(); for (int i = 0; i < 1024; i++) { buf.append(s1).append(s2).append(s3); } final String s = buf.toString(); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16, StandardCharsets.UTF_8.newEncoder()); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); chbuffer.append(s); outbuffer.writeLine(chbuffer, outputStream); outbuffer.flush(outputStream); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.UTF_8.newDecoder()); final ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); chbuffer.clear(); inBuffer.readLine(chbuffer, inputStream); Assertions.assertEquals(s, chbuffer.toString()); } @Test void testNonAsciiReadWriteLine() throws Exception { final String s1 = constructString(SWISS_GERMAN_HELLO); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16, StandardCharsets.UTF_8.newEncoder()); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); for (int i = 0; i < 10; i++) { chbuffer.clear(); chbuffer.append(s1); outbuffer.writeLine(chbuffer, outputStream); } chbuffer.clear(); outbuffer.writeLine(chbuffer, outputStream); outbuffer.flush(outputStream); final long bytesWritten = outbuffer.getMetrics().getBytesTransferred(); final long expected = ((s1.getBytes(StandardCharsets.UTF_8).length + 2)) * 10L + 2; Assertions.assertEquals(expected, bytesWritten); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.UTF_8.newDecoder()); final ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); for (int i = 0; i < 10; i++) { chbuffer.clear(); final int len = inBuffer.readLine(chbuffer, inputStream); Assertions.assertEquals(len, SWISS_GERMAN_HELLO.length); Assertions.assertEquals(s1, chbuffer.toString()); } chbuffer.clear(); Assertions.assertEquals(0, inBuffer.readLine(chbuffer, inputStream)); chbuffer.clear(); Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream)); chbuffer.clear(); Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream)); final long bytesRead = inBuffer.getMetrics().getBytesTransferred(); Assertions.assertEquals(expected, bytesRead); } @Test void testUnmappableInputActionReport() { final String s = "This text contains a circumflex \u0302 !!!"; final CharsetEncoder encoder = StandardCharsets.ISO_8859_1.newEncoder(); encoder.onMalformedInput(CodingErrorAction.IGNORE); encoder.onUnmappableCharacter(CodingErrorAction.REPORT); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16, encoder); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final CharArrayBuffer chbuffer = new CharArrayBuffer(32); chbuffer.append(s); Assertions.assertThrows(CharacterCodingException.class, () -> outbuffer.writeLine(chbuffer, outputStream)); } @Test void testUnmappableInputActionReplace() throws Exception { final String s = "This text contains a circumflex \u0302 !!!"; final CharsetEncoder encoder = StandardCharsets.ISO_8859_1.newEncoder(); encoder.onMalformedInput(CodingErrorAction.IGNORE); encoder.onUnmappableCharacter(CodingErrorAction.REPLACE); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16, encoder); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final CharArrayBuffer chbuffer = new CharArrayBuffer(32); chbuffer.append(s); outbuffer.writeLine(chbuffer, outputStream); outbuffer.flush(outputStream); final String result = new String(outputStream.toByteArray(), StandardCharsets.ISO_8859_1); Assertions.assertEquals("This text contains a circumflex ? !!!\r\n", result); } @Test void testUnmappableInputActionIgnore() throws Exception { final String s = "This text contains a circumflex \u0302 !!!"; final CharsetEncoder encoder = StandardCharsets.ISO_8859_1.newEncoder(); encoder.onMalformedInput(CodingErrorAction.IGNORE); encoder.onUnmappableCharacter(CodingErrorAction.IGNORE); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16, encoder); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final CharArrayBuffer chbuffer = new CharArrayBuffer(32); chbuffer.append(s); outbuffer.writeLine(chbuffer, outputStream); outbuffer.flush(outputStream); final String result = new String(outputStream.toByteArray(), StandardCharsets.ISO_8859_1); Assertions.assertEquals("This text contains a circumflex !!!\r\n", result); } @Test void testMalformedInputActionReport() { final byte[] tmp = constructString(SWISS_GERMAN_HELLO).getBytes(StandardCharsets.ISO_8859_1); final CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); decoder.onMalformedInput(CodingErrorAction.REPORT); decoder.onUnmappableCharacter(CodingErrorAction.IGNORE); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, decoder); final ByteArrayInputStream inputStream = new ByteArrayInputStream(tmp); final CharArrayBuffer chbuffer = new CharArrayBuffer(32); Assertions.assertThrows(CharacterCodingException.class, () -> inBuffer.readLine(chbuffer, inputStream)); } @Test void testMalformedInputActionReplace() throws Exception { final byte[] tmp = constructString(SWISS_GERMAN_HELLO).getBytes(StandardCharsets.ISO_8859_1); final CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); decoder.onMalformedInput(CodingErrorAction.REPLACE); decoder.onUnmappableCharacter(CodingErrorAction.IGNORE); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, decoder); final ByteArrayInputStream inputStream = new ByteArrayInputStream(tmp); final CharArrayBuffer chbuffer = new CharArrayBuffer(32); inBuffer.readLine(chbuffer, inputStream); Assertions.assertEquals("Gr\ufffdezi_z\ufffdm\ufffd", chbuffer.toString()); } @Test void testMalformedInputActionIgnore() throws Exception { final byte[] tmp = constructString(SWISS_GERMAN_HELLO).getBytes(StandardCharsets.ISO_8859_1); final CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); decoder.onMalformedInput(CodingErrorAction.IGNORE); decoder.onUnmappableCharacter(CodingErrorAction.IGNORE); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, decoder); final ByteArrayInputStream inputStream = new ByteArrayInputStream(tmp); final CharArrayBuffer chbuffer = new CharArrayBuffer(32); inBuffer.readLine(chbuffer, inputStream); Assertions.assertEquals("Grezi_zm", chbuffer.toString()); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TimeoutByteArrayInputStream.java0100644 0000000 0000000 00000007275 00000250600 030460 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.io; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; /** * Test class similar to {@link ByteArrayInputStream} that throws if encounters * value zero '\000' in the source byte array. */ class TimeoutByteArrayInputStream extends InputStream { private final byte[] buf; private int pos; protected final int count; public TimeoutByteArrayInputStream(final byte[] buf, final int off, final int len) { super(); this.buf = buf; this.pos = off; this.count = Math.min(off + len, buf.length); } public TimeoutByteArrayInputStream(final byte[] buf) { this(buf, 0, buf.length); } @Override public int read() throws IOException { if (this.pos < this.count) { return -1; } final int v = this.buf[this.pos++] & 0xff; if (v != 0) { return v; } throw new InterruptedIOException("Timeout"); } @Override public int read(final byte b[], final int off, final int len) throws IOException { if (b == null) { throw new NullPointerException(); } else if (off < 0 || off > b.length || len < 0 || (off + len) > b.length || (off + len) < 0) { throw new IndexOutOfBoundsException("off: " + off + " len: " + len + " b.length: " + b.length); } if (len == 0) { return 0; } if (this.pos >= this.count) { return -1; } int chunk = len; if (this.pos + len > this.count) { chunk = this.count - this.pos; } if (chunk <= 0) { return 0; } if ((this.buf[this.pos] & 0xff) == 0) { this.pos++; throw new InterruptedIOException("Timeout"); } for (int i = 0; i < chunk; i++) { final int v = this.buf[this.pos] & 0xff; if (v == 0) { return i; } b[off + i] = (byte) v; this.pos++; } return chunk; } @Override public long skip(final long n) { long chunk = n; if (this.pos + n > this.count) { chunk = this.count - this.pos; } if (chunk < 0) { return 0; } this.pos += chunk; return chunk; } @Override public int available() { return this.count - this.pos; } @Override public boolean markSupported() { return false; } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/0040755 0000000 0000000 00000000000 00000250600 022316 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/CodecTestUtils.java0100644 0000000 0000000 00000004451 00000250600 026060 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; class CodecTestUtils { public static ByteBuffer wrap(final String s) { return ByteBuffer.wrap(s.getBytes(StandardCharsets.US_ASCII)); } public static String convert(final ByteBuffer src) { src.flip(); final StringBuilder buffer = new StringBuilder(src.remaining()); while (src.hasRemaining()) { buffer.append((char)(src.get() & 0xff)); } return buffer.toString(); } public static String readFromFile(final File file) throws Exception { final FileInputStream filestream = new FileInputStream(file); try (InputStreamReader reader = new InputStreamReader(filestream)) { final StringBuilder buffer = new StringBuilder(); final char[] tmp = new char[2048]; int l; while ((l = reader.read(tmp)) != -1) { buffer.append(tmp, 0, l); } return buffer.toString(); } } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/ContentDecoderMock.java0100644 0000000 0000000 00000004223 00000250600 026671 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.util.List; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.nio.ContentDecoder; public class ContentDecoderMock implements ContentDecoder { private final ReadableByteChannel channel; private boolean completed; public ContentDecoderMock(final ReadableByteChannel channel) { super(); this.channel = channel; } @Override public int read(final ByteBuffer dst) throws IOException { if (this.completed) { return -1; } final int bytesRead = this.channel.read(dst); if (bytesRead == -1) { this.completed = true; } return bytesRead; } @Override public boolean isCompleted() { return this.completed; } @Override public List getTrailers() { return null; } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/ContentEncoderMock.java0100644 0000000 0000000 00000004005 00000250600 026701 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.nio.SessionOutputBuffer; import org.apache.hc.core5.util.Asserts; public class ContentEncoderMock extends AbstractContentEncoder { public ContentEncoderMock( final WritableByteChannel channel, final SessionOutputBuffer buffer, final BasicHttpTransportMetrics metrics) { super(channel, buffer, metrics); } @Override public int write(final ByteBuffer src) throws IOException { if (src == null) { return 0; } Asserts.check(!isCompleted(), "Decoding process already completed"); return this.channel().write(src); } } ././@LongLink0100644 0000000 0000000 00000000155 00000250600 011612 Lustar 0000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestAbstractHttp1StreamDuplexerCapacityWindow.javahttpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestAbstractHttp1StreamDuplexerCapacityWin0100644 0000000 0000000 00000007361 00000250600 032612 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.channels.SelectionKey; import org.apache.hc.core5.http.impl.nio.AbstractHttp1StreamDuplexer.CapacityWindow; import org.apache.hc.core5.reactor.IOSession; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; class TestAbstractHttp1StreamDuplexerCapacityWindow { @Mock private IOSession ioSession; private AutoCloseable closeable; @BeforeEach void prepareMocks() { closeable = MockitoAnnotations.openMocks(this); } @AfterEach void releaseMocks() throws Exception { closeable.close(); } @Test void testWindowUpdate() throws IOException { final CapacityWindow window = new CapacityWindow(0, ioSession); window.update(1); Assertions.assertEquals(1, window.getWindow()); Mockito.verify(ioSession).setEvent(ArgumentMatchers.eq(SelectionKey.OP_READ)); Mockito.verifyNoMoreInteractions(ioSession); } @Test void testRemoveCapacity() { final CapacityWindow window = new CapacityWindow(1, ioSession); window.removeCapacity(1); Assertions.assertEquals(0, window.getWindow()); Mockito.verify(ioSession).clearEvent(ArgumentMatchers.eq(SelectionKey.OP_READ)); Mockito.verifyNoMoreInteractions(ioSession); } @Test void noReadsSetAfterWindowIsClosed() throws IOException { final CapacityWindow window = new CapacityWindow(1, ioSession); window.close(); window.update(1); Mockito.verifyNoInteractions(ioSession); } @Test void windowCannotUnderflow() { final CapacityWindow window = new CapacityWindow(Integer.MIN_VALUE, ioSession); window.removeCapacity(1); Assertions.assertEquals(Integer.MIN_VALUE, window.getWindow()); } @Test void windowCannotOverflow() throws IOException { final CapacityWindow window = new CapacityWindow(Integer.MAX_VALUE, ioSession); window.update(1); Assertions.assertEquals(Integer.MAX_VALUE, window.getWindow()); } @Test void noChangesIfUpdateIsNonPositive() throws IOException { final CapacityWindow window = new CapacityWindow(1, ioSession); window.update(0); window.update(-1); Assertions.assertEquals(1, window.getWindow()); Mockito.verifyNoInteractions(ioSession); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestChunkDecoder.java0100644 0000000 0000000 00000063044 00000250600 026363 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.nio.charset.StandardCharsets; import java.util.List; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.MalformedChunkCodingException; import org.apache.hc.core5.http.MessageConstraintException; import org.apache.hc.core5.http.ReadableByteChannelMock; import org.apache.hc.core5.http.TruncatedChunkException; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.nio.SessionInputBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Simple tests for {@link ChunkDecoder}. */ class TestChunkDecoder { @Test void testBasicDecoding() throws Exception { final String s = "5\r\n01234\r\n5\r\n56789\r\n6\r\nabcdef\r\n0\r\n\r\n"; final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics); final ByteBuffer dst = ByteBuffer.allocate(1024); int bytesRead = decoder.read(dst); Assertions.assertEquals(16, bytesRead); Assertions.assertEquals("0123456789abcdef", CodecTestUtils.convert(dst)); final List trailers = decoder.getTrailers(); Assertions.assertNull(trailers); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(-1, bytesRead); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals("[chunk-coded; completed: true]", decoder.toString()); } @Test void testComplexDecoding() throws Exception { final String s = "10;key=\"value\"\r\n1234567890123456\r\n" + "5\r\n12345\r\n5\r\n12345\r\n0\r\nFooter1: abcde\r\nFooter2: fghij\r\n\r\n"; final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics); final ByteBuffer dst = ByteBuffer.allocate(1024); int bytesRead = 0; while (dst.hasRemaining() && !decoder.isCompleted()) { final int i = decoder.read(dst); if (i > 0) { bytesRead += i; } } Assertions.assertEquals(26, bytesRead); Assertions.assertEquals("12345678901234561234512345", CodecTestUtils.convert(dst)); final List trailers = decoder.getTrailers(); Assertions.assertEquals(2, trailers.size()); Assertions.assertEquals("Footer1", trailers.get(0).getName()); Assertions.assertEquals("abcde", trailers.get(0).getValue()); Assertions.assertEquals("Footer2", trailers.get(1).getName()); Assertions.assertEquals("fghij", trailers.get(1).getValue()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(-1, bytesRead); Assertions.assertTrue(decoder.isCompleted()); } @Test void testDecodingWithSmallBuffer() throws Exception { final String s1 = "5\r\n01234\r\n5\r\n5678"; final String s2 = "9\r\n6\r\nabcdef\r\n0\r\n\r\n"; final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {s1, s2}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics); final ByteBuffer dst = ByteBuffer.allocate(1024); final ByteBuffer tmp = ByteBuffer.allocate(4); int bytesRead = 0; while (dst.hasRemaining() && !decoder.isCompleted()) { final int i = decoder.read(tmp); if (i > 0) { bytesRead += i; } tmp.flip(); dst.put(tmp); tmp.compact(); } Assertions.assertEquals(16, bytesRead); Assertions.assertEquals("0123456789abcdef", CodecTestUtils.convert(dst)); Assertions.assertTrue(decoder.isCompleted()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(-1, bytesRead); Assertions.assertTrue(decoder.isCompleted()); } @Test void testMalformedChunk() { final String s = "5\r\n01234----------------------------------------------------------" + "-----------------------------------------------------------------------------" + "-----------------------------------------------------------------------------"; final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(32, 32, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics); final ByteBuffer dst = ByteBuffer.allocate(1024); Assertions.assertThrows(MalformedChunkCodingException.class, () -> decoder.read(dst)); } @Test void testIncompleteChunkDecoding() throws Exception { final String[] chunks = { "10;", "key=\"value\"\r", "\n123456789012345", "6\r\n5\r\n12", "345\r\n6\r", "\nabcdef\r", "\n0\r\nFoot", "er1: abcde\r\nFooter2: f", "ghij\r\n\r\n" }; final ReadableByteChannel channel = new ReadableByteChannelMock( chunks, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ByteBuffer dst = ByteBuffer.allocate(1024); final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics); int bytesRead = 0; while (dst.hasRemaining() && !decoder.isCompleted()) { final int i = decoder.read(dst); if (i > 0) { bytesRead += i; } } Assertions.assertEquals(27, bytesRead); Assertions.assertEquals("123456789012345612345abcdef", CodecTestUtils.convert(dst)); Assertions.assertTrue(decoder.isCompleted()); final List trailers = decoder.getTrailers(); Assertions.assertEquals(2, trailers.size()); Assertions.assertEquals("Footer1", trailers.get(0).getName()); Assertions.assertEquals("abcde", trailers.get(0).getValue()); Assertions.assertEquals("Footer2", trailers.get(1).getName()); Assertions.assertEquals("fghij", trailers.get(1).getValue()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(-1, bytesRead); Assertions.assertTrue(decoder.isCompleted()); } @Test void testMalformedChunkSizeDecoding() { final String s = "5\r\n01234\r\n5zz\r\n56789\r\n6\r\nabcdef\r\n0\r\n\r\n"; final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics); final ByteBuffer dst = ByteBuffer.allocate(1024); Assertions.assertThrows(MalformedChunkCodingException.class, () -> decoder.read(dst)); } @Test void testMalformedChunkEndingDecoding() { final String s = "5\r\n01234\r\n5\r\n56789\r\r6\r\nabcdef\r\n0\r\n\r\n"; final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics); final ByteBuffer dst = ByteBuffer.allocate(1024); Assertions.assertThrows(MalformedChunkCodingException.class, () -> decoder.read(dst)); } @Test void testMalformedChunkTruncatedChunk() throws Exception { final String s = "3\r\n12"; final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics); final ByteBuffer dst = ByteBuffer.allocate(1024); Assertions.assertEquals(2, decoder.read(dst)); Assertions.assertThrows(TruncatedChunkException.class, () -> decoder.read(dst)); } @Test void testFoldedFooters() throws Exception { final String s = "10;key=\"value\"\r\n1234567890123456\r\n" + "5\r\n12345\r\n5\r\n12345\r\n0\r\nFooter1: abcde\r\n \r\n fghij\r\n\r\n"; final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics); final ByteBuffer dst = ByteBuffer.allocate(1024); final int bytesRead = decoder.read(dst); Assertions.assertEquals(26, bytesRead); Assertions.assertEquals("12345678901234561234512345", CodecTestUtils.convert(dst)); final List trailers = decoder.getTrailers(); Assertions.assertEquals(1, trailers.size()); Assertions.assertEquals("Footer1", trailers.get(0).getName()); Assertions.assertEquals("abcde fghij", trailers.get(0).getValue()); } @Test void testMalformedFooters() { final String s = "10;key=\"value\"\r\n1234567890123456\r\n" + "5\r\n12345\r\n5\r\n12345\r\n0\r\nFooter1 abcde\r\n\r\n"; final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics); final ByteBuffer dst = ByteBuffer.allocate(1024); Assertions.assertThrows(IOException.class, () -> decoder.read(dst)); } @Test void testMissingLastCRLF() { final String s = "10\r\n1234567890123456\r\n" + "5\r\n12345\r\n5\r\n12345"; final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics); final ByteBuffer dst = ByteBuffer.allocate(1024); Assertions.assertThrows(MalformedChunkCodingException.class, () -> { while (dst.hasRemaining() && !decoder.isCompleted()) { decoder.read(dst); } }); } @Test void testMissingClosingChunk() { final String s = "10\r\n1234567890123456\r\n" + "5\r\n12345\r\n5\r\n12345\r\n"; final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics); final ByteBuffer dst = ByteBuffer.allocate(1024); Assertions.assertThrows(ConnectionClosedException.class, () -> { long bytesRead = 0; try { while (dst.hasRemaining() && !decoder.isCompleted()) { final int i = decoder.read(dst); if (i > 0) { bytesRead += i; } } } catch (final MalformedChunkCodingException ex) { Assertions.assertEquals(26L, bytesRead); Assertions.assertEquals("12345678901234561234512345", CodecTestUtils.convert(dst)); Assertions.assertTrue(decoder.isCompleted()); throw ex; } }); } @Test void testReadingWitSmallBuffer() throws Exception { final String s = "10\r\n1234567890123456\r\n" + "40\r\n12345678901234561234567890123456" + "12345678901234561234567890123456\r\n0\r\n"; final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics); final ByteBuffer dst = ByteBuffer.allocate(1024); final ByteBuffer tmp = ByteBuffer.allocate(10); int bytesRead = 0; while (dst.hasRemaining() && !decoder.isCompleted()) { final int i = decoder.read(tmp); if (i > 0) { bytesRead += i; tmp.flip(); dst.put(tmp); tmp.compact(); } } Assertions.assertEquals(80, bytesRead); Assertions.assertEquals("12345678901234561234567890123456" + "12345678901234561234567890123456" + "1234567890123456", CodecTestUtils.convert(dst)); Assertions.assertTrue(decoder.isCompleted()); } @Test void testEndOfStreamConditionReadingFooters() throws Exception { final String s = "10\r\n1234567890123456\r\n" + "5\r\n12345\r\n5\r\n12345\r\n0\r\n"; final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics); final ByteBuffer dst = ByteBuffer.allocate(1024); int bytesRead = 0; while (dst.hasRemaining() && !decoder.isCompleted()) { final int i = decoder.read(dst); if (i > 0) { bytesRead += i; } } Assertions.assertEquals(26, bytesRead); Assertions.assertEquals("12345678901234561234512345", CodecTestUtils.convert(dst)); Assertions.assertTrue(decoder.isCompleted()); } @Test void testTooLongChunkHeader() throws Exception { final String s = "5; and some very looooong comment\r\n12345\r\n0\r\n"; final ReadableByteChannel channel1 = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics1 = new BasicHttpTransportMetrics(); final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(1024, 256); final ChunkDecoder decoder1 = new ChunkDecoder(channel1, inbuf1, metrics1); final ByteBuffer dst = ByteBuffer.allocate(1024); while (dst.hasRemaining() && !decoder1.isCompleted()) { decoder1.read(dst); } Assertions.assertEquals("12345", CodecTestUtils.convert(dst)); Assertions.assertTrue(decoder1.isCompleted()); final ReadableByteChannel channel2 = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(1024, 256, 10); final BasicHttpTransportMetrics metrics2 = new BasicHttpTransportMetrics(); final ChunkDecoder decoder2 = new ChunkDecoder(channel2, inbuf2, metrics2); dst.clear(); Assertions.assertThrows(MessageConstraintException.class, () -> decoder2.read(dst)); } @Test void testTooLongFooter() throws Exception { final String s = "10\r\n1234567890123456\r\n" + "0\r\nFooter1: looooooooooooooooooooooooooooooooooooooooooooooooooooooog\r\n\r\n"; final ReadableByteChannel channel1 = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(1024, 256, 0); final BasicHttpTransportMetrics metrics1 = new BasicHttpTransportMetrics(); final ChunkDecoder decoder1 = new ChunkDecoder(channel1, inbuf1, metrics1); final ByteBuffer dst = ByteBuffer.allocate(1024); final int bytesRead = decoder1.read(dst); Assertions.assertEquals(16, bytesRead); Assertions.assertEquals("1234567890123456", CodecTestUtils.convert(dst)); final List trailers = decoder1.getTrailers(); Assertions.assertNotNull(trailers); Assertions.assertEquals(1, trailers.size()); final ReadableByteChannel channel2 = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(1024, 256, 25, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics2 = new BasicHttpTransportMetrics(); final ChunkDecoder decoder2 = new ChunkDecoder(channel2, inbuf2, metrics2); dst.clear(); Assertions.assertThrows(MessageConstraintException.class, () -> decoder2.read(dst)); } @Test void testTooLongFoldedFooter() throws Exception { final String s = "10\r\n1234567890123456\r\n" + "0\r\nFooter1: blah\r\n blah\r\n blah\r\n blah\r\n blah\r\n blah\r\n blah\r\n blah\r\n\r\n"; final ReadableByteChannel channel1 = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics1 = new BasicHttpTransportMetrics(); final ChunkDecoder decoder1 = new ChunkDecoder(channel1, inbuf1, metrics1); final ByteBuffer dst = ByteBuffer.allocate(1024); final int bytesRead = decoder1.read(dst); Assertions.assertEquals(16, bytesRead); Assertions.assertEquals("1234567890123456", CodecTestUtils.convert(dst)); final List trailers = decoder1.getTrailers(); Assertions.assertNotNull(trailers); Assertions.assertEquals(1, trailers.size()); final Http1Config http1Config = Http1Config.custom() .setMaxLineLength(25) .build(); final ReadableByteChannel channel2 = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(1024, 256,0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics2 = new BasicHttpTransportMetrics(); final ChunkDecoder decoder2 = new ChunkDecoder(channel2, inbuf2, http1Config, metrics2); dst.clear(); Assertions.assertThrows(MessageConstraintException.class, () -> decoder2.read(dst)); } @Test void testTooManyFooters() throws Exception { final String s = "10\r\n1234567890123456\r\n" + "0\r\nFooter1: blah\r\nFooter2: blah\r\nFooter3: blah\r\nFooter4: blah\r\n\r\n"; final ReadableByteChannel channel1 = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(1024, 256,0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics1 = new BasicHttpTransportMetrics(); final ChunkDecoder decoder1 = new ChunkDecoder(channel1, inbuf1, metrics1); final ByteBuffer dst = ByteBuffer.allocate(1024); final int bytesRead = decoder1.read(dst); Assertions.assertEquals(16, bytesRead); Assertions.assertEquals("1234567890123456", CodecTestUtils.convert(dst)); final List trailers = decoder1.getTrailers(); Assertions.assertNotNull(trailers); Assertions.assertEquals(4, trailers.size()); final Http1Config http1Config = Http1Config.custom() .setMaxHeaderCount(3).build(); final ReadableByteChannel channel2 = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics2 = new BasicHttpTransportMetrics(); final ChunkDecoder decoder2 = new ChunkDecoder(channel2, inbuf2, http1Config, metrics2); dst.clear(); Assertions.assertThrows(MessageConstraintException.class, () -> decoder2.read(dst)); } @Test void testInvalidConstructor() { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); Assertions.assertThrows(NullPointerException.class, () -> new ChunkDecoder(null, null, null)); Assertions.assertThrows(NullPointerException.class, () -> new ChunkDecoder(channel, inbuf, null)); } @Test void testInvalidInput() { final String s = "10;key=\"value\"\r\n1234567890123456\r\n" + "5\r\n12345\r\n5\r\n12345\r\n0\r\nFooter1 abcde\r\n\r\n"; final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics); Assertions.assertThrows(NullPointerException.class, () -> decoder.read(null)); } @Test void testHugeChunk() throws Exception { final String s = "1234567890abcdef\r\n0123456789abcdef"; final ReadableByteChannel channel = new ReadableByteChannelMock(new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics); final ByteBuffer dst = ByteBuffer.allocate(4); int bytesRead = decoder.read(dst); Assertions.assertEquals(4, bytesRead); Assertions.assertEquals("0123", CodecTestUtils.convert(dst)); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(4, bytesRead); Assertions.assertEquals("4567", CodecTestUtils.convert(dst)); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestChunkEncoder.java0100644 0000000 0000000 00000023166 00000250600 026376 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; import org.apache.hc.core5.http.WritableByteChannelMock; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.nio.SessionOutputBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; /** * Simple tests for {@link ChunkEncoder}. */ class TestChunkEncoder { @Test void testBasicCoding() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkEncoder encoder = new ChunkEncoder(channel, outbuf, metrics); encoder.write(CodecTestUtils.wrap("12345")); encoder.write(CodecTestUtils.wrap("678")); encoder.write(CodecTestUtils.wrap("90")); encoder.complete(); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertTrue(encoder.isCompleted()); Assertions.assertEquals("5\r\n12345\r\n3\r\n678\r\n2\r\n90\r\n0\r\n\r\n", s); Assertions.assertEquals("[chunk-coded; completed: true]", encoder.toString()); } @Test void testChunkNoExceed() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 16); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkEncoder encoder = new ChunkEncoder(channel, outbuf, metrics); encoder.write(CodecTestUtils.wrap("1234")); encoder.complete(); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertTrue(encoder.isCompleted()); Assertions.assertEquals("4\r\n1234\r\n0\r\n\r\n", s); } @Test // See HTTPCORE-239 void testLimitedChannel() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(16, 16); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(16, 16); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkEncoder encoder = new ChunkEncoder(channel, outbuf, metrics); // fill up the channel channel.write(CodecTestUtils.wrap("0123456789ABCDEF")); // fill up the out buffer outbuf.write(CodecTestUtils.wrap("0123456789ABCDEF")); final ByteBuffer src = CodecTestUtils.wrap("0123456789ABCDEF"); Assertions.assertEquals(0, encoder.write(src)); Assertions.assertEquals(0, encoder.write(src)); Assertions.assertEquals(0, encoder.write(src)); // should not be able to copy any bytes, until we flush the channel and buffer channel.reset(); outbuf.flush(channel); channel.reset(); Assertions.assertEquals(10, encoder.write(src)); channel.flush(); Assertions.assertEquals(6, encoder.write(src)); channel.flush(); Assertions.assertEquals(0, encoder.write(src)); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("4\r\n0123\r\n4\r\n4567\r\n2\r\n89\r\n4\r\nABCD\r\n2\r\nEF\r\n", s); } @Test void testBufferFragments() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(1024)); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 1024); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkEncoder encoder = new ChunkEncoder(channel, outbuf, metrics, 1024); Assertions.assertEquals(16, encoder.write(CodecTestUtils.wrap("0123456789ABCDEF"))); Assertions.assertEquals(16, encoder.write(CodecTestUtils.wrap("0123456789ABCDEF"))); Assertions.assertEquals(16, encoder.write(CodecTestUtils.wrap("0123456789ABCDEF"))); Mockito.verify(channel, Mockito.never()).write(ArgumentMatchers.any()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("10\r\n0123456789ABCDEF\r\n10\r\n0123456789ABCDEF\r\n" + "10\r\n0123456789ABCDEF\r\n", s); } @Test void testChunkExceed() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(16, 16); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkEncoder encoder = new ChunkEncoder(channel, outbuf, metrics); final ByteBuffer src = CodecTestUtils.wrap("0123456789ABCDEF"); Assertions.assertEquals(16, encoder.write(src)); Assertions.assertEquals(0, src.remaining()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("4\r\n0123\r\n4\r\n4567\r\n4\r\n89AB\r\n4\r\nCDEF\r\n", s); } @Test void testCodingEmptyBuffer() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkEncoder encoder = new ChunkEncoder(channel, outbuf, metrics); encoder.write(CodecTestUtils.wrap("12345")); encoder.write(CodecTestUtils.wrap("678")); encoder.write(CodecTestUtils.wrap("90")); final ByteBuffer empty = ByteBuffer.allocate(100); empty.flip(); encoder.write(empty); encoder.write(null); encoder.complete(); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertTrue(encoder.isCompleted()); Assertions.assertEquals("5\r\n12345\r\n3\r\n678\r\n2\r\n90\r\n0\r\n\r\n", s); } @Test void testCodingCompleted() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkEncoder encoder = new ChunkEncoder(channel, outbuf, metrics); encoder.write(CodecTestUtils.wrap("12345")); encoder.write(CodecTestUtils.wrap("678")); encoder.write(CodecTestUtils.wrap("90")); encoder.complete(); final ByteBuffer wrapped = CodecTestUtils.wrap("more stuff"); Assertions.assertThrows(IllegalStateException.class, () -> encoder.write(wrapped)); Assertions.assertThrows(IllegalStateException.class, () -> encoder.complete()); } @Test void testInvalidConstructor() { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); Assertions.assertThrows(NullPointerException.class, () -> new ChunkEncoder(null, null, null)); Assertions.assertThrows(NullPointerException.class, () -> new ChunkEncoder(channel, null, null)); Assertions.assertThrows(NullPointerException.class, () -> new ChunkEncoder(channel, outbuf, null)); } @Test void testTrailers() throws IOException { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final ChunkEncoder encoder = new ChunkEncoder(channel, outbuf, metrics, 0); encoder.write(CodecTestUtils.wrap("1")); encoder.write(CodecTestUtils.wrap("23")); encoder.complete(Arrays.asList(new BasicHeader("E", ""), new BasicHeader("Y", "Z"))); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertTrue(encoder.isCompleted()); Assertions.assertEquals("1\r\n1\r\n2\r\n23\r\n0\r\nE: \r\nY: Z\r\n\r\n", s); Assertions.assertEquals("[chunk-coded; completed: true]", encoder.toString()); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestExpandableBuffer.java0100644 0000000 0000000 00000006756 00000250600 027231 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import static org.hamcrest.MatcherAssert.assertThat; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; class TestExpandableBuffer { @Test void testBasics() { final ExpandableBuffer buffer = new ExpandableBuffer(16); assertThat(buffer.mode(), CoreMatchers.equalTo(ExpandableBuffer.Mode.INPUT)); assertThat(buffer.hasData(), CoreMatchers.equalTo(false)); buffer.setInputMode(); buffer.buffer().put(new byte[] { 0, 1, 2, 3, 4, 5}); assertThat(buffer.hasData(), CoreMatchers.equalTo(true)); assertThat(buffer.length(), CoreMatchers.equalTo(6)); assertThat(buffer.buffer().capacity(), CoreMatchers.equalTo(16)); assertThat(buffer.mode(), CoreMatchers.equalTo(ExpandableBuffer.Mode.OUTPUT)); buffer.setInputMode(); buffer.buffer().put(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); assertThat(buffer.length(), CoreMatchers.equalTo(16)); assertThat(buffer.buffer().capacity(), CoreMatchers.equalTo(16)); assertThat(buffer.mode(), CoreMatchers.equalTo(ExpandableBuffer.Mode.OUTPUT)); buffer.setInputMode(); buffer.ensureCapacity(22); buffer.buffer().put(new byte[] { 0, 1, 2, 3, 4, 5}); assertThat(buffer.length(), CoreMatchers.equalTo(22)); assertThat(buffer.buffer().capacity(), CoreMatchers.equalTo(22)); assertThat(buffer.mode(), CoreMatchers.equalTo(ExpandableBuffer.Mode.OUTPUT)); buffer.clear(); assertThat(buffer.mode(), CoreMatchers.equalTo(ExpandableBuffer.Mode.INPUT)); assertThat(buffer.hasData(), CoreMatchers.equalTo(false)); assertThat(buffer.capacity(), CoreMatchers.equalTo(22)); } @Test void testAdjustCapacity() { final ExpandableBuffer buffer = new ExpandableBuffer(16); assertThat(buffer.capacity(), CoreMatchers.equalTo(16)); buffer.ensureCapacity(21); assertThat(buffer.capacity(), CoreMatchers.equalTo(21)); buffer.ensureAdjustedCapacity(22); assertThat(buffer.capacity(), CoreMatchers.equalTo(1024)); buffer.ensureAdjustedCapacity(1024); assertThat(buffer.capacity(), CoreMatchers.equalTo(1024)); buffer.ensureAdjustedCapacity(1025); assertThat(buffer.capacity(), CoreMatchers.equalTo(2048)); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestIdentityDecoder.java0100644 0000000 0000000 00000035420 00000250600 027101 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ReadableByteChannelMock; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.nio.SessionInputBuffer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Simple tests for {@link LengthDelimitedDecoder}. */ class TestIdentityDecoder { private File tmpfile; protected File createTempFile() throws IOException { this.tmpfile = File.createTempFile("testFile", ".txt"); return this.tmpfile; } @AfterEach void deleteTempFile() { if (this.tmpfile != null && this.tmpfile.exists()) { this.tmpfile.delete(); } } @Test void testBasicDecoding() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityDecoder decoder = new IdentityDecoder(channel, inbuf, metrics); final ByteBuffer dst = ByteBuffer.allocate(1024); int bytesRead = decoder.read(dst); Assertions.assertEquals(6, bytesRead); Assertions.assertEquals("stuff;", CodecTestUtils.convert(dst)); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(6, metrics.getBytesTransferred()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(10, bytesRead); Assertions.assertEquals("more stuff", CodecTestUtils.convert(dst)); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(16, metrics.getBytesTransferred()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(-1, bytesRead); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(16, metrics.getBytesTransferred()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(-1, bytesRead); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(16, metrics.getBytesTransferred()); Assertions.assertEquals("[identity; completed: true]", decoder.toString()); } @Test void testDecodingFromSessionBuffer() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); inbuf.fill(channel); Assertions.assertEquals(6, inbuf.length()); final IdentityDecoder decoder = new IdentityDecoder(channel, inbuf, metrics); final ByteBuffer dst = ByteBuffer.allocate(1024); int bytesRead = decoder.read(dst); Assertions.assertEquals(6, bytesRead); Assertions.assertEquals("stuff;", CodecTestUtils.convert(dst)); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(0, metrics.getBytesTransferred()); // doesn't count if from session buffer dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(10, bytesRead); Assertions.assertEquals("more stuff", CodecTestUtils.convert(dst)); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(10, metrics.getBytesTransferred()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(-1, bytesRead); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(10, metrics.getBytesTransferred()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(-1, bytesRead); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(10, metrics.getBytesTransferred()); } @Test void testBasicDecodingFile() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff; ", "more stuff; ", "a lot more stuff!"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityDecoder decoder = new IdentityDecoder( channel, inbuf, metrics); createTempFile(); try (RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw")) { final FileChannel fchannel = testfile.getChannel(); long pos = 0; while (!decoder.isCompleted()) { final long bytesRead = decoder.transfer(fchannel, pos, 10); if (bytesRead > 0) { pos += bytesRead; } } Assertions.assertEquals(testfile.length(), metrics.getBytesTransferred()); } Assertions.assertEquals("stuff; more stuff; a lot more stuff!", CodecTestUtils.readFromFile(this.tmpfile)); } @Test void testDecodingFileWithBufferedSessionData() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff; ", "more stuff; ", "a lot more stuff!"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityDecoder decoder = new IdentityDecoder( channel, inbuf, metrics); final int i = inbuf.fill(channel); Assertions.assertEquals(7, i); createTempFile(); try (RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw")) { final FileChannel fchannel = testfile.getChannel(); long pos = 0; while (!decoder.isCompleted()) { final long bytesRead = decoder.transfer(fchannel, pos, 10); if (bytesRead > 0) { pos += bytesRead; } } // count everything except the initial 7 bytes that went to the session buffer Assertions.assertEquals(testfile.length() - 7, metrics.getBytesTransferred()); } Assertions.assertEquals("stuff; more stuff; a lot more stuff!", CodecTestUtils.readFromFile(this.tmpfile)); } @Test void testDecodingFileWithOffsetAndBufferedSessionData() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff; ", "more stuff; ", "a lot more stuff!"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityDecoder decoder = new IdentityDecoder( channel, inbuf, metrics); final int i = inbuf.fill(channel); Assertions.assertEquals(7, i); final byte[] beginning = "beginning; ".getBytes(StandardCharsets.US_ASCII); createTempFile(); RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw"); try { testfile.write(beginning); } finally { testfile.close(); } testfile = new RandomAccessFile(this.tmpfile, "rw"); try { final FileChannel fchannel = testfile.getChannel(); long pos = beginning.length; while (!decoder.isCompleted()) { if (testfile.length() < pos) { testfile.setLength(pos); } final long bytesRead = decoder.transfer(fchannel, pos, 10); if (bytesRead > 0) { pos += bytesRead; } } // count everything except the initial 7 bytes that went to the session buffer Assertions.assertEquals(testfile.length() - 7 - beginning.length, metrics.getBytesTransferred()); } finally { testfile.close(); } Assertions.assertEquals("beginning; stuff; more stuff; a lot more stuff!", CodecTestUtils.readFromFile(this.tmpfile)); } @Test void testDecodingFileWithLimit() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff; more stuff; ", "a lot more stuff!"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityDecoder decoder = new IdentityDecoder(channel, inbuf, metrics); final int i = inbuf.fill(channel); Assertions.assertEquals(19, i); createTempFile(); try (RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw")) { final FileChannel fchannel = testfile.getChannel(); long pos = 0; // transferred from buffer long bytesRead = decoder.transfer(fchannel, pos, 1); Assertions.assertEquals(1, bytesRead); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(0, metrics.getBytesTransferred()); pos += bytesRead; bytesRead = decoder.transfer(fchannel, pos, 2); Assertions.assertEquals(2, bytesRead); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(0, metrics.getBytesTransferred()); pos += bytesRead; bytesRead = decoder.transfer(fchannel, pos, 17); Assertions.assertEquals(16, bytesRead); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(0, metrics.getBytesTransferred()); pos += bytesRead; // transferred from channel bytesRead = decoder.transfer(fchannel, pos, 1); Assertions.assertEquals(1, bytesRead); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(1, metrics.getBytesTransferred()); pos += bytesRead; bytesRead = decoder.transfer(fchannel, pos, 2); Assertions.assertEquals(2, bytesRead); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(3, metrics.getBytesTransferred()); pos += bytesRead; bytesRead = decoder.transfer(fchannel, pos, 15); Assertions.assertEquals(14, bytesRead); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(17, metrics.getBytesTransferred()); pos += bytesRead; bytesRead = decoder.transfer(fchannel, pos, 1); Assertions.assertEquals(-1, bytesRead); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(17, metrics.getBytesTransferred()); } Assertions.assertEquals("stuff; more stuff; a lot more stuff!", CodecTestUtils.readFromFile(this.tmpfile)); } @Test void testWriteBeyondFileSize() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"a"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityDecoder decoder = new IdentityDecoder( channel, inbuf, metrics); createTempFile(); try (RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw")) { Assertions.assertEquals(0, testfile.length()); final FileChannel fchannel = testfile.getChannel(); Assertions.assertThrows(IOException.class, () -> decoder.transfer(fchannel, 5, 10)); } } @Test void testInvalidConstructor() { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); Assertions.assertThrows(NullPointerException.class, () -> new IdentityDecoder(null, null, null)); Assertions.assertThrows(NullPointerException.class, () -> new IdentityDecoder(channel, null, null)); Assertions.assertThrows(NullPointerException.class, () -> new IdentityDecoder(channel, inbuf, null)); } @Test void testInvalidInput() { final String s = "stuff"; final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityDecoder decoder = new IdentityDecoder(channel, inbuf, metrics); Assertions.assertThrows(NullPointerException.class, () -> decoder.read(null)); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestIdentityEncoder.java0100644 0000000 0000000 00000056447 00000250600 027127 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.WritableByteChannelMock; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.nio.SessionOutputBuffer; import org.apache.hc.core5.util.CharArrayBuffer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; /** * Simple tests for {@link IdentityEncoder}. */ class TestIdentityEncoder { private File tmpfile; protected File createTempFile() throws IOException { this.tmpfile = File.createTempFile("testFile", ".txt"); return this.tmpfile; } @AfterEach void deleteTempFile() { if (this.tmpfile != null && this.tmpfile.exists()) { this.tmpfile.delete(); } } @Test void testBasicCoding() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityEncoder encoder = new IdentityEncoder(channel, outbuf, metrics); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); encoder.complete(); Assertions.assertTrue(encoder.isCompleted()); Assertions.assertEquals(5, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("stuff", s); Assertions.assertEquals("[identity; completed: true]", encoder.toString()); } @Test void testCodingEmptySrcBuffer() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityEncoder encoder = new IdentityEncoder(channel, outbuf, metrics); encoder.write(CodecTestUtils.wrap("stuff")); final ByteBuffer empty = ByteBuffer.allocate(100); empty.flip(); encoder.write(empty); encoder.write(null); encoder.complete(); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertTrue(encoder.isCompleted()); Assertions.assertEquals("stuff", s); } @Test void testCodingCompleted() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityEncoder encoder = new IdentityEncoder(channel, outbuf, metrics); encoder.write(CodecTestUtils.wrap("stuff")); encoder.complete(); final ByteBuffer wrapped = CodecTestUtils.wrap("more stuff"); Assertions.assertThrows(IllegalStateException.class, () -> encoder.write(wrapped)); } @Test void testInvalidConstructor() { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); Assertions.assertThrows(NullPointerException.class, () -> new IdentityEncoder(null, null, null)); Assertions.assertThrows(NullPointerException.class, () -> new IdentityEncoder(channel, null, null)); Assertions.assertThrows(NullPointerException.class, () -> new IdentityEncoder(channel, outbuf, null)); } @Test void testCodingFromFile() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityEncoder encoder = new IdentityEncoder(channel, outbuf, metrics); createTempFile(); RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw"); try { testfile.write("stuff;".getBytes(StandardCharsets.US_ASCII)); testfile.write("more stuff".getBytes(StandardCharsets.US_ASCII)); } finally { testfile.close(); } testfile = new RandomAccessFile(this.tmpfile, "rw"); try { final FileChannel fchannel = testfile.getChannel(); encoder.transfer(fchannel, 0, 20); } finally { testfile.close(); } final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertFalse(encoder.isCompleted()); Assertions.assertEquals("stuff;more stuff", s); } @Test void testCodingEmptyFile() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityEncoder encoder = new IdentityEncoder(channel, outbuf, metrics); encoder.write(CodecTestUtils.wrap("stuff;")); //Create an empty file createTempFile(); RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw"); testfile.close(); testfile = new RandomAccessFile(this.tmpfile, "rw"); try { final FileChannel fchannel = testfile.getChannel(); encoder.transfer(fchannel, 0, 20); encoder.write(CodecTestUtils.wrap("more stuff")); } finally { testfile.close(); } final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertFalse(encoder.isCompleted()); Assertions.assertEquals("stuff;more stuff", s); } @Test void testCodingFromFileFlushBuffer() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityEncoder encoder = new IdentityEncoder(channel, outbuf, metrics); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); chbuffer.append("header"); outbuf.writeLine(chbuffer); createTempFile(); RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw"); try { testfile.write("stuff;".getBytes(StandardCharsets.US_ASCII)); testfile.write("more stuff".getBytes(StandardCharsets.US_ASCII)); } finally { testfile.close(); } testfile = new RandomAccessFile(this.tmpfile, "rw"); try { final FileChannel fchannel = testfile.getChannel(); encoder.transfer(fchannel, 0, 20); } finally { testfile.close(); } final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertFalse(encoder.isCompleted()); Assertions.assertEquals("header\r\nstuff;more stuff", s); } @Test void testCodingFromFileChannelSaturated() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64, 4); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityEncoder encoder = new IdentityEncoder(channel, outbuf, metrics); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); chbuffer.append("header"); outbuf.writeLine(chbuffer); createTempFile(); RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw"); try { testfile.write("stuff".getBytes(StandardCharsets.US_ASCII)); } finally { testfile.close(); } testfile = new RandomAccessFile(this.tmpfile, "rw"); try { final FileChannel fchannel = testfile.getChannel(); encoder.transfer(fchannel, 0, 20); encoder.transfer(fchannel, 0, 20); } finally { testfile.close(); } final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertFalse(encoder.isCompleted()); Assertions.assertEquals("head", s); } @Test void testCodingNoFragmentBuffering() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); chbuffer.append("header"); outbuf.writeLine(chbuffer); final IdentityEncoder encoder = new IdentityEncoder(channel, outbuf, metrics, 0); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Mockito.verify(channel, Mockito.times(2)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.never()).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(1)).flush(channel); Assertions.assertEquals(13, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("header\r\nstuff", s); } @Test void testCodingFragmentBuffering() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); chbuffer.append("header"); outbuf.writeLine(chbuffer); final IdentityEncoder encoder = new IdentityEncoder(channel, outbuf, metrics, 32); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Mockito.verify(channel, Mockito.never()).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(1)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.never()).flush(channel); Assertions.assertEquals(0, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("header\r\nstuff", s); } @Test void testCodingFragmentBufferingMultipleFragments() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityEncoder encoder = new IdentityEncoder(channel, outbuf, metrics, 32); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(10, encoder.write(CodecTestUtils.wrap("more stuff"))); Mockito.verify(channel, Mockito.never()).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(3)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.never()).flush(channel); Assertions.assertEquals(0, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("stuff-more stuff", s); } @Test void testCodingFragmentBufferingLargeFragment() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); chbuffer.append("header"); outbuf.writeLine(chbuffer); final IdentityEncoder encoder = new IdentityEncoder(channel, outbuf, metrics, 2); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Mockito.verify(channel, Mockito.times(2)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.never()).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(1)).flush(channel); Assertions.assertEquals(13, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("header\r\nstuff", s); } @Test void testCodingFragmentBufferingTinyFragments() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityEncoder encoder = new IdentityEncoder(channel, outbuf, metrics, 1); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(10, encoder.write(CodecTestUtils.wrap("more stuff"))); Mockito.verify(channel, Mockito.times(5)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(3)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(3)).flush(channel); Assertions.assertEquals(18, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("stuff---more stuff", s); } @Test void testCodingFragmentBufferingTinyFragments2() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityEncoder encoder = new IdentityEncoder(channel, outbuf, metrics, 2); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(10, encoder.write(CodecTestUtils.wrap("more stuff"))); Mockito.verify(channel, Mockito.times(4)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(3)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(2)).flush(channel); Assertions.assertEquals(18, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("stuff---more stuff", s); } @Test void testCodingFragmentBufferingTinyFragments3() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityEncoder encoder = new IdentityEncoder(channel, outbuf, metrics, 3); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(2, encoder.write(CodecTestUtils.wrap("--"))); Assertions.assertEquals(10, encoder.write(CodecTestUtils.wrap("more stuff"))); Mockito.verify(channel, Mockito.times(4)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(5)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(2)).flush(channel); Assertions.assertEquals(21, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("stuff------more stuff", s); } @Test void testCodingFragmentBufferingBufferFlush() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityEncoder encoder = new IdentityEncoder(channel, outbuf, metrics, 8); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Assertions.assertEquals(6, encoder.write(CodecTestUtils.wrap("-stuff"))); Mockito.verify(channel, Mockito.times(1)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(3)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(1)).flush(channel); Assertions.assertEquals(8, metrics.getBytesTransferred()); Assertions.assertEquals(3, outbuf.length()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("stuff-stuff", s); } @Test void testCodingFragmentBufferingBufferFlush2() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityEncoder encoder = new IdentityEncoder(channel, outbuf, metrics, 8); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Assertions.assertEquals(16, encoder.write(CodecTestUtils.wrap("-much more stuff"))); Mockito.verify(channel, Mockito.times(2)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(1)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(1)).flush(channel); Assertions.assertEquals(21, metrics.getBytesTransferred()); Assertions.assertEquals(0, outbuf.length()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("stuff-much more stuff", s); } @Test void testCodingFragmentBufferingChannelSaturated() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64, 8)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityEncoder encoder = new IdentityEncoder(channel, outbuf, metrics, 3); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(0, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(0, encoder.write(CodecTestUtils.wrap("more stuff"))); Mockito.verify(channel, Mockito.times(5)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(6)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(4)).flush(channel); Assertions.assertEquals(8, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("stuff---", s); Assertions.assertEquals(3, outbuf.length()); } @Test void testCodingFragmentBufferingChannelSaturated2() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64, 8)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final IdentityEncoder encoder = new IdentityEncoder(channel, outbuf, metrics, 8); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("much more stuff"))); Mockito.verify(channel, Mockito.times(3)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(3)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(1)).flush(channel); Assertions.assertEquals(8, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("stuff--m", s); Assertions.assertEquals(0, outbuf.length()); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestLengthDelimitedDecoder.java0100644 0000000 0000000 00000057517 00000250600 030365 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.ReadableByteChannelMock; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.nio.SessionInputBuffer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Simple tests for {@link LengthDelimitedDecoder}. */ class TestLengthDelimitedDecoder { private File tmpfile; protected File createTempFile() throws IOException { this.tmpfile = File.createTempFile("testFile", ".txt"); return this.tmpfile; } @AfterEach void deleteTempFile() { if (this.tmpfile != null && this.tmpfile.exists()) { this.tmpfile.delete(); } } @Test void testBasicDecoding() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder( channel, inbuf, metrics, 16); final ByteBuffer dst = ByteBuffer.allocate(1024); int bytesRead = decoder.read(dst); Assertions.assertEquals(6, bytesRead); Assertions.assertEquals("stuff;", CodecTestUtils.convert(dst)); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(6, metrics.getBytesTransferred()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(10, bytesRead); Assertions.assertEquals("more stuff", CodecTestUtils.convert(dst)); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(16, metrics.getBytesTransferred()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(-1, bytesRead); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(16, metrics.getBytesTransferred()); Assertions.assertEquals("[content length: 16; pos: 16; completed: true]", decoder.toString()); } @Test void testCodingBeyondContentLimit() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] { "stuff;", "more stuff; and a lot more stuff"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder( channel, inbuf, metrics, 16); final ByteBuffer dst = ByteBuffer.allocate(1024); int bytesRead = decoder.read(dst); Assertions.assertEquals(6, bytesRead); Assertions.assertEquals("stuff;", CodecTestUtils.convert(dst)); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(6, metrics.getBytesTransferred()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(10, bytesRead); Assertions.assertEquals("more stuff", CodecTestUtils.convert(dst)); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(16, metrics.getBytesTransferred()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(-1, bytesRead); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(16, metrics.getBytesTransferred()); } @Test void testBasicDecodingSmallBuffer() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder( channel, inbuf, metrics, 16); final ByteBuffer dst = ByteBuffer.allocate(4); int bytesRead = decoder.read(dst); Assertions.assertEquals(4, bytesRead); Assertions.assertEquals("stuf", CodecTestUtils.convert(dst)); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(4, metrics.getBytesTransferred()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(2, bytesRead); Assertions.assertEquals("f;", CodecTestUtils.convert(dst)); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(6, metrics.getBytesTransferred()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(4, bytesRead); Assertions.assertEquals("more", CodecTestUtils.convert(dst)); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(10, metrics.getBytesTransferred()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(4, bytesRead); Assertions.assertEquals(" stu", CodecTestUtils.convert(dst)); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(14, metrics.getBytesTransferred()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(2, bytesRead); Assertions.assertEquals("ff", CodecTestUtils.convert(dst)); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(16, metrics.getBytesTransferred()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(-1, bytesRead); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(16, metrics.getBytesTransferred()); } @Test void testDecodingFromSessionBuffer1() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); inbuf.fill(channel); Assertions.assertEquals(6, inbuf.length()); final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder( channel, inbuf, metrics, 16); final ByteBuffer dst = ByteBuffer.allocate(1024); int bytesRead = decoder.read(dst); Assertions.assertEquals(6, bytesRead); Assertions.assertEquals("stuff;", CodecTestUtils.convert(dst)); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(0, metrics.getBytesTransferred()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(10, bytesRead); Assertions.assertEquals("more stuff", CodecTestUtils.convert(dst)); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(10, metrics.getBytesTransferred()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(-1, bytesRead); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(10, metrics.getBytesTransferred()); } @Test void testDecodingFromSessionBuffer2() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] { "stuff;", "more stuff; and a lot more stuff"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); inbuf.fill(channel); inbuf.fill(channel); Assertions.assertEquals(38, inbuf.length()); final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder( channel, inbuf, metrics, 16); final ByteBuffer dst = ByteBuffer.allocate(1024); int bytesRead = decoder.read(dst); Assertions.assertEquals(16, bytesRead); Assertions.assertEquals("stuff;more stuff", CodecTestUtils.convert(dst)); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(0, metrics.getBytesTransferred()); dst.clear(); bytesRead = decoder.read(dst); Assertions.assertEquals(-1, bytesRead); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(0, metrics.getBytesTransferred()); } /* ----------------- FileChannel Part testing --------------------------- */ @Test void testBasicDecodingFile() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff; ", "more stuff; ", "a lot more stuff!!!"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder( channel, inbuf, metrics, 36); createTempFile(); try (RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw")) { final FileChannel fchannel = testfile.getChannel(); long pos = 0; while (!decoder.isCompleted()) { final long bytesRead = decoder.transfer(fchannel, pos, 10); if (bytesRead > 0) { pos += bytesRead; } } } Assertions.assertEquals(this.tmpfile.length(), metrics.getBytesTransferred()); Assertions.assertEquals("stuff; more stuff; a lot more stuff!", CodecTestUtils.readFromFile(this.tmpfile)); } @Test void testDecodingFileWithBufferedSessionData() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff; ", "more stuff; ", "a lot more stuff!!!"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder( channel, inbuf, metrics, 36); final int i = inbuf.fill(channel); Assertions.assertEquals(7, i); createTempFile(); try (RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw")) { final FileChannel fchannel = testfile.getChannel(); long pos = 0; while (!decoder.isCompleted()) { final long bytesRead = decoder.transfer(fchannel, pos, 10); if (bytesRead > 0) { pos += bytesRead; } } } Assertions.assertEquals(this.tmpfile.length() - 7, metrics.getBytesTransferred()); Assertions.assertEquals("stuff; more stuff; a lot more stuff!", CodecTestUtils.readFromFile(this.tmpfile)); } @Test void testDecodingFileWithOffsetAndBufferedSessionData() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff; ", "more stuff; ", "a lot more stuff!"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder( channel, inbuf, metrics, 36); final int i = inbuf.fill(channel); Assertions.assertEquals(7, i); final byte[] beginning = "beginning; ".getBytes(StandardCharsets.US_ASCII); createTempFile(); RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw"); try { testfile.write(beginning); } finally { testfile.close(); } testfile = new RandomAccessFile(this.tmpfile, "rw"); try { final FileChannel fchannel = testfile.getChannel(); long pos = beginning.length; while (!decoder.isCompleted()) { if (testfile.length() < pos) { testfile.setLength(pos); } final long bytesRead = decoder.transfer(fchannel, pos, 10); if (bytesRead > 0) { pos += bytesRead; } } } finally { testfile.close(); } // count everything except the initial 7 bytes that went to the session buffer Assertions.assertEquals(this.tmpfile.length() - 7 - beginning.length, metrics.getBytesTransferred()); Assertions.assertEquals("beginning; stuff; more stuff; a lot more stuff!", CodecTestUtils.readFromFile(this.tmpfile)); } @Test void testDecodingFileWithLimit() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff; more stuff; ", "a lot more stuff!!!"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder(channel, inbuf, metrics, 36); final int i = inbuf.fill(channel); Assertions.assertEquals(19, i); createTempFile(); try (RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw")) { final FileChannel fchannel = testfile.getChannel(); long pos = 0; // transferred from buffer long bytesRead = decoder.transfer(fchannel, pos, 1); Assertions.assertEquals(1, bytesRead); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(0, metrics.getBytesTransferred()); pos += bytesRead; bytesRead = decoder.transfer(fchannel, pos, 2); Assertions.assertEquals(2, bytesRead); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(0, metrics.getBytesTransferred()); pos += bytesRead; bytesRead = decoder.transfer(fchannel, pos, 17); Assertions.assertEquals(16, bytesRead); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(0, metrics.getBytesTransferred()); pos += bytesRead; // transferred from channel bytesRead = decoder.transfer(fchannel, pos, 1); Assertions.assertEquals(1, bytesRead); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(1, metrics.getBytesTransferred()); pos += bytesRead; bytesRead = decoder.transfer(fchannel, pos, 2); Assertions.assertEquals(2, bytesRead); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(3, metrics.getBytesTransferred()); pos += bytesRead; bytesRead = decoder.transfer(fchannel, pos, 15); Assertions.assertEquals(14, bytesRead); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(17, metrics.getBytesTransferred()); pos += bytesRead; bytesRead = decoder.transfer(fchannel, pos, 1); Assertions.assertEquals(-1, bytesRead); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(17, metrics.getBytesTransferred()); } Assertions.assertEquals("stuff; more stuff; a lot more stuff!", CodecTestUtils.readFromFile(this.tmpfile)); } @Test void testWriteBeyondFileSize() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"a"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder( channel, inbuf, metrics, 1); createTempFile(); try (RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw")) { final FileChannel fchannel = testfile.getChannel(); Assertions.assertEquals(0, testfile.length()); Assertions.assertThrows(IOException.class, () -> decoder.transfer(fchannel, 5, 10)); } } @Test void testCodingBeyondContentLimitFile() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] { "stuff;", "more stuff; and a lot more stuff"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder( channel, inbuf, metrics, 16); createTempFile(); try (RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw")) { final FileChannel fchannel = testfile.getChannel(); long bytesRead = decoder.transfer(fchannel, 0, 6); Assertions.assertEquals(6, bytesRead); Assertions.assertFalse(decoder.isCompleted()); Assertions.assertEquals(6, metrics.getBytesTransferred()); bytesRead = decoder.transfer(fchannel,0 , 10); Assertions.assertEquals(10, bytesRead); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(16, metrics.getBytesTransferred()); bytesRead = decoder.transfer(fchannel, 0, 1); Assertions.assertEquals(-1, bytesRead); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(16, metrics.getBytesTransferred()); } } @Test void testInvalidConstructor() { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); Assertions.assertThrows(NullPointerException.class, () -> new LengthDelimitedDecoder(null, null, null, 10)); Assertions.assertThrows(NullPointerException.class, () -> new LengthDelimitedDecoder(channel, null, null, 10)); Assertions.assertThrows(NullPointerException.class, () -> new LengthDelimitedDecoder(channel, inbuf, null, 10)); Assertions.assertThrows(IllegalArgumentException.class, () -> new LengthDelimitedDecoder(channel, inbuf, metrics, -10)); } @Test void testInvalidInput() { final String s = "stuff"; final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder( channel, inbuf, metrics, 3); Assertions.assertThrows(NullPointerException.class, () -> decoder.read(null)); } @Test void testZeroLengthDecoding() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder( channel, inbuf, metrics, 0); final ByteBuffer dst = ByteBuffer.allocate(1024); final int bytesRead = decoder.read(dst); Assertions.assertEquals(-1, bytesRead); Assertions.assertTrue(decoder.isCompleted()); Assertions.assertEquals(0, metrics.getBytesTransferred()); } @Test void testTruncatedContent() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"1234567890"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder( channel, inbuf, metrics, 20); final ByteBuffer dst = ByteBuffer.allocate(1024); final int bytesRead = decoder.read(dst); Assertions.assertEquals(10, bytesRead); Assertions.assertThrows(ConnectionClosedException.class, () -> decoder.read(dst)); } @Test void testTruncatedContentWithFile() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"1234567890"}, StandardCharsets.US_ASCII); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder( channel, inbuf, metrics, 20); createTempFile(); try (RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw")) { final FileChannel fchannel = testfile.getChannel(); final long bytesRead = decoder.transfer(fchannel, 0, Integer.MAX_VALUE); Assertions.assertEquals(10, bytesRead); Assertions.assertThrows(ConnectionClosedException.class, () -> decoder.transfer(fchannel, 0, Integer.MAX_VALUE)); } } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestLengthDelimitedEncoder.java0100644 0000000 0000000 00000070201 00000250600 030360 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.WritableByteChannelMock; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.nio.SessionOutputBuffer; import org.apache.hc.core5.util.CharArrayBuffer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; /** * Simple tests for {@link LengthDelimitedEncoder}. */ class TestLengthDelimitedEncoder { private File tmpfile; protected File createTempFile() throws IOException { this.tmpfile = File.createTempFile("testFile", ".txt"); return this.tmpfile; } @AfterEach void deleteTempFile() { if (this.tmpfile != null && this.tmpfile.exists()) { this.tmpfile.delete(); } } @Test void testBasicCoding() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder( channel, outbuf, metrics, 16); encoder.write(CodecTestUtils.wrap("stuff;")); encoder.write(CodecTestUtils.wrap("more stuff")); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertTrue(encoder.isCompleted()); Assertions.assertEquals("stuff;more stuff", s); Assertions.assertEquals("[content length: 16; pos: 16; completed: true]", encoder.toString()); } @Test void testCodingBeyondContentLimit() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder( channel, outbuf, metrics, 16); encoder.write(CodecTestUtils.wrap("stuff;")); encoder.write(CodecTestUtils.wrap("more stuff; and a lot more stuff")); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertTrue(encoder.isCompleted()); Assertions.assertEquals("stuff;more stuff", s); } @Test void testCodingEmptyBuffer() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder( channel, outbuf, metrics, 16); encoder.write(CodecTestUtils.wrap("stuff;")); final ByteBuffer empty = ByteBuffer.allocate(100); empty.flip(); encoder.write(empty); encoder.write(null); encoder.write(CodecTestUtils.wrap("more stuff")); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertTrue(encoder.isCompleted()); Assertions.assertEquals("stuff;more stuff", s); } @Test void testCodingCompleted() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder( channel, outbuf, metrics, 5); encoder.write(CodecTestUtils.wrap("stuff")); final ByteBuffer wrapped = CodecTestUtils.wrap("more stuff"); Assertions.assertThrows(IllegalStateException.class, () -> encoder.write(wrapped)); } @Test void testInvalidConstructor() { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); Assertions.assertThrows(NullPointerException.class, () -> new LengthDelimitedEncoder(null, null, null, 10)); Assertions.assertThrows(NullPointerException.class, () -> new LengthDelimitedEncoder(channel, null, null, 10)); Assertions.assertThrows(NullPointerException.class, () -> new LengthDelimitedEncoder(channel, outbuf, null, 10)); Assertions.assertThrows(IllegalArgumentException.class, () -> new LengthDelimitedEncoder(channel, outbuf, metrics, -10)); } @Test void testCodingBeyondContentLimitFromFile() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder( channel, outbuf, metrics, 16); createTempFile(); RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw"); try { testfile.write("stuff;".getBytes(StandardCharsets.US_ASCII)); testfile.write("more stuff; and a lot more stuff".getBytes(StandardCharsets.US_ASCII)); } finally { testfile.close(); } testfile = new RandomAccessFile(this.tmpfile, "rw"); try { final FileChannel fchannel = testfile.getChannel(); encoder.transfer(fchannel, 0, 20); } finally { testfile.close(); } final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertTrue(encoder.isCompleted()); Assertions.assertEquals("stuff;more stuff", s); } @Test void testCodingEmptyFile() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder( channel, outbuf, metrics, 16); encoder.write(CodecTestUtils.wrap("stuff;")); //Create an empty file createTempFile(); RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw"); testfile.close(); testfile = new RandomAccessFile(this.tmpfile, "rw"); try { final FileChannel fchannel = testfile.getChannel(); encoder.transfer(fchannel, 0, 20); encoder.write(CodecTestUtils.wrap("more stuff")); } finally { testfile.close(); } final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertTrue(encoder.isCompleted()); Assertions.assertEquals("stuff;more stuff", s); } @Test void testCodingCompletedFromFile() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder( channel, outbuf, metrics, 5); encoder.write(CodecTestUtils.wrap("stuff")); createTempFile(); try (final RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw")) { testfile.write("more stuff".getBytes(StandardCharsets.US_ASCII)); } try (final RandomAccessFile file = new RandomAccessFile(this.tmpfile, "rw"); final FileChannel fchannel = file.getChannel()) { Assertions.assertThrows(IllegalStateException.class, () -> encoder.transfer(fchannel, 0, 10)); } } @Test void testCodingFromFileSmaller() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder( channel, outbuf, metrics, 16); createTempFile(); RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw"); try { testfile.write("stuff;".getBytes(StandardCharsets.US_ASCII)); testfile.write("more stuff".getBytes(StandardCharsets.US_ASCII)); } finally { testfile.close(); } testfile = new RandomAccessFile(this.tmpfile, "rw"); try { final FileChannel fchannel = testfile.getChannel(); encoder.transfer(fchannel, 0, 20); } finally { testfile.close(); } final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertTrue(encoder.isCompleted()); Assertions.assertEquals("stuff;more stuff", s); } @Test void testCodingFromFileFlushBuffer() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder( channel, outbuf, metrics, 16); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); chbuffer.append("header"); outbuf.writeLine(chbuffer); createTempFile(); RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw"); try { testfile.write("stuff;".getBytes(StandardCharsets.US_ASCII)); testfile.write("more stuff".getBytes(StandardCharsets.US_ASCII)); } finally { testfile.close(); } testfile = new RandomAccessFile(this.tmpfile, "rw"); try { final FileChannel fchannel = testfile.getChannel(); encoder.transfer(fchannel, 0, 20); } finally { testfile.close(); } final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertTrue(encoder.isCompleted()); Assertions.assertEquals("header\r\nstuff;more stuff", s); } @Test void testCodingFromFileChannelSaturated() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64, 4); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder( channel, outbuf, metrics, 16); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); chbuffer.append("header"); outbuf.writeLine(chbuffer); createTempFile(); RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw"); try { testfile.write("stuff".getBytes(StandardCharsets.US_ASCII)); } finally { testfile.close(); } testfile = new RandomAccessFile(this.tmpfile, "rw"); try { final FileChannel fchannel = testfile.getChannel(); encoder.transfer(fchannel, 0, 20); encoder.transfer(fchannel, 0, 20); } finally { testfile.close(); } final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertFalse(encoder.isCompleted()); Assertions.assertEquals("head", s); } @Test void testCodingNoFragmentBuffering() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); chbuffer.append("header"); outbuf.writeLine(chbuffer); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder(channel, outbuf, metrics, 100, 0); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Mockito.verify(channel, Mockito.times(2)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.never()).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(1)).flush(channel); Assertions.assertEquals(13, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("header\r\nstuff", s); } @Test void testCodingFragmentBuffering() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); chbuffer.append("header"); outbuf.writeLine(chbuffer); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder(channel, outbuf, metrics, 100, 32); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Mockito.verify(channel, Mockito.never()).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(1)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.never()).flush(channel); Assertions.assertEquals(0, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("header\r\nstuff", s); } @Test void testCodingFragmentBufferingMultipleFragments() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder(channel, outbuf, metrics, 100, 32); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(10, encoder.write(CodecTestUtils.wrap("more stuff"))); Mockito.verify(channel, Mockito.never()).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(3)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.never()).flush(channel); Assertions.assertEquals(0, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("stuff-more stuff", s); } @Test void testCodingFragmentBufferingMultipleFragmentsBeyondContentLimit() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder(channel, outbuf, metrics, 16, 32); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(10, encoder.write(CodecTestUtils.wrap("more stuff; and a lot more stuff"))); Mockito.verify(channel, Mockito.never()).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(3)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.never()).flush(channel); Assertions.assertEquals(0, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("stuff-more stuff", s); } @Test void testCodingFragmentBufferingLargeFragment() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); chbuffer.append("header"); outbuf.writeLine(chbuffer); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder(channel, outbuf, metrics, 100, 2); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Mockito.verify(channel, Mockito.times(2)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.never()).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(1)).flush(channel); Assertions.assertEquals(13, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("header\r\nstuff", s); } @Test void testCodingFragmentBufferingTinyFragments() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder(channel, outbuf, metrics, 100, 1); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(10, encoder.write(CodecTestUtils.wrap("more stuff"))); Mockito.verify(channel, Mockito.times(5)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(3)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(3)).flush(channel); Assertions.assertEquals(18, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("stuff---more stuff", s); } @Test void testCodingFragmentBufferingTinyFragments2() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder(channel, outbuf, metrics, 100, 2); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(10, encoder.write(CodecTestUtils.wrap("more stuff"))); Mockito.verify(channel, Mockito.times(4)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(3)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(2)).flush(channel); Assertions.assertEquals(18, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("stuff---more stuff", s); } @Test void testCodingFragmentBufferingTinyFragments3() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder(channel, outbuf, metrics, 100, 3); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(2, encoder.write(CodecTestUtils.wrap("--"))); Assertions.assertEquals(10, encoder.write(CodecTestUtils.wrap("more stuff"))); Mockito.verify(channel, Mockito.times(4)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(5)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(2)).flush(channel); Assertions.assertEquals(21, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("stuff------more stuff", s); } @Test void testCodingFragmentBufferingBufferFlush() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder(channel, outbuf, metrics, 100, 8); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Assertions.assertEquals(6, encoder.write(CodecTestUtils.wrap("-stuff"))); Mockito.verify(channel, Mockito.times(1)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(3)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(1)).flush(channel); Assertions.assertEquals(8, metrics.getBytesTransferred()); Assertions.assertEquals(3, outbuf.length()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("stuff-stuff", s); } @Test void testCodingFragmentBufferingBufferFlush2() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder(channel, outbuf, metrics, 100, 8); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Assertions.assertEquals(16, encoder.write(CodecTestUtils.wrap("-much more stuff"))); Mockito.verify(channel, Mockito.times(2)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(1)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(1)).flush(channel); Assertions.assertEquals(21, metrics.getBytesTransferred()); Assertions.assertEquals(0, outbuf.length()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("stuff-much more stuff", s); } @Test void testCodingFragmentBufferingChannelSaturated() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64, 8)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder(channel, outbuf, metrics, 100, 3); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(0, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(0, encoder.write(CodecTestUtils.wrap("more stuff"))); Mockito.verify(channel, Mockito.times(5)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(6)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(4)).flush(channel); Assertions.assertEquals(8, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("stuff---", s); Assertions.assertEquals(3, outbuf.length()); } @Test void testCodingFragmentBufferingChannelSaturated2() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(64, 8)); final SessionOutputBuffer outbuf = Mockito.spy(new SessionOutputBufferImpl(1024, 128)); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); final LengthDelimitedEncoder encoder = new LengthDelimitedEncoder(channel, outbuf, metrics, 100, 8); Assertions.assertEquals(5, encoder.write(CodecTestUtils.wrap("stuff"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("-"))); Assertions.assertEquals(1, encoder.write(CodecTestUtils.wrap("much more stuff"))); Mockito.verify(channel, Mockito.times(3)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(3)).write(ArgumentMatchers.any()); Mockito.verify(outbuf, Mockito.times(1)).flush(channel); Assertions.assertEquals(8, metrics.getBytesTransferred()); outbuf.flush(channel); final String s = channel.dump(StandardCharsets.US_ASCII); Assertions.assertEquals("stuff--m", s); Assertions.assertEquals(0, outbuf.length()); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestSessionInOutBuffers.java0100644 0000000 0000000 00000062072 00000250600 027744 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.nio; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.MessageConstraintException; import org.apache.hc.core5.http.nio.SessionInputBuffer; import org.apache.hc.core5.http.nio.SessionOutputBuffer; import org.apache.hc.core5.util.CharArrayBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Simple tests for {@link SessionInputBuffer} and {@link SessionOutputBuffer}. */ class TestSessionInOutBuffers { private static WritableByteChannel newChannel(final ByteArrayOutputStream outStream) { return Channels.newChannel(outStream); } private static ReadableByteChannel newChannel(final byte[] bytes) { return Channels.newChannel(new ByteArrayInputStream(bytes)); } private static ReadableByteChannel newChannel(final String s, final Charset charset) { return Channels.newChannel(new ByteArrayInputStream(s.getBytes(charset))); } private static ReadableByteChannel newChannel(final String s) { return newChannel(s, StandardCharsets.US_ASCII); } @Test void testReadLineChunks() throws Exception { final SessionInputBuffer inbuf = new SessionInputBufferImpl(16, 16); ReadableByteChannel channel = newChannel("One\r\nTwo\r\nThree"); inbuf.fill(channel); final CharArrayBuffer line = new CharArrayBuffer(64); line.clear(); Assertions.assertTrue(inbuf.readLine(line, false)); Assertions.assertEquals("One", line.toString()); line.clear(); Assertions.assertTrue(inbuf.readLine(line, false)); Assertions.assertEquals("Two", line.toString()); line.clear(); Assertions.assertFalse(inbuf.readLine(line, false)); channel = newChannel("\r\nFour"); inbuf.fill(channel); line.clear(); Assertions.assertTrue(inbuf.readLine(line, false)); Assertions.assertEquals("Three", line.toString()); inbuf.fill(channel); line.clear(); Assertions.assertTrue(inbuf.readLine(line, true)); Assertions.assertEquals("Four", line.toString()); line.clear(); Assertions.assertFalse(inbuf.readLine(line, true)); } @Test void testLineLimit() throws Exception { final String s = "LoooooooooooooooooooooooooOOOOOOOOOOOOOOOOOOoooooooooooooooooooooong line\r\n"; final CharArrayBuffer line = new CharArrayBuffer(64); final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(128, 128); final ReadableByteChannel channel1 = newChannel(s); inbuf1.fill(channel1); Assertions.assertTrue(inbuf1.readLine(line, false)); line.clear(); final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(128, 128, 10); final ReadableByteChannel channel2 = newChannel(s); inbuf2.fill(channel2); Assertions.assertThrows(MessageConstraintException.class, () -> inbuf2.readLine(line, false)); } @Test void testLineLimitBufferFull() throws Exception { final String s = "LoooooooooooooooooooooooooOOOOOOOOOOOOOOOOOOoooooooooooooooooooooong line\r\n"; final CharArrayBuffer line = new CharArrayBuffer(64); final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(32, 32); final ReadableByteChannel channel1 = newChannel(s); inbuf1.fill(channel1); Assertions.assertFalse(inbuf1.readLine(line, false)); line.clear(); final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(32, 32,10); final ReadableByteChannel channel2 = newChannel(s); inbuf2.fill(channel2); Assertions.assertThrows(MessageConstraintException.class, () -> inbuf2.readLine(line, false)); } @Test void testWriteLineChunks() throws Exception { final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(16, 16); final SessionInputBuffer inbuf = new SessionInputBufferImpl(16, 16, 0); ReadableByteChannel inChannel = newChannel("One\r\nTwo\r\nThree"); inbuf.fill(inChannel); final CharArrayBuffer line = new CharArrayBuffer(64); line.clear(); Assertions.assertTrue(inbuf.readLine(line, false)); Assertions.assertEquals("One", line.toString()); outbuf.writeLine(line); line.clear(); Assertions.assertTrue(inbuf.readLine(line, false)); Assertions.assertEquals("Two", line.toString()); outbuf.writeLine(line); line.clear(); Assertions.assertFalse(inbuf.readLine(line, false)); inChannel = newChannel("\r\nFour"); inbuf.fill(inChannel); line.clear(); Assertions.assertTrue(inbuf.readLine(line, false)); Assertions.assertEquals("Three", line.toString()); outbuf.writeLine(line); inbuf.fill(inChannel); line.clear(); Assertions.assertTrue(inbuf.readLine(line, true)); Assertions.assertEquals("Four", line.toString()); outbuf.writeLine(line); line.clear(); Assertions.assertFalse(inbuf.readLine(line, true)); final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); final WritableByteChannel outChannel = newChannel(outStream); outbuf.flush(outChannel); final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("One\r\nTwo\r\nThree\r\nFour\r\n", s); } @Test void testNonASCIIWriteLine() throws Exception { final String testString = "123\u010Anew-header-from-some-header:injected-value"; final String expectedResult = "123?new-header-from-some-header:injected-value"; final CharArrayBuffer chbuffer = new CharArrayBuffer(32); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 16); chbuffer.clear(); chbuffer.append(testString); outbuf.writeLine(chbuffer); //this write operation should have no effect outbuf.writeLine(null); final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); final WritableByteChannel outChannel = newChannel(outStream); outbuf.flush(outChannel); final ReadableByteChannel channel = newChannel(outStream.toByteArray()); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 16, 0); inbuf.fill(channel); chbuffer.clear(); inbuf.readLine(chbuffer, true); Assertions.assertEquals(expectedResult, chbuffer.toString()); } @Test void testBasicReadWriteLine() throws Exception { final String[] teststrs = new String[5]; teststrs[0] = "Hello"; teststrs[1] = "This string should be much longer than the size of the line buffer " + "which is only 16 bytes for this test"; final StringBuilder buffer = new StringBuilder(); for (int i = 0; i < 15; i++) { buffer.append("123456789 "); } buffer.append("and stuff like that"); teststrs[2] = buffer.toString(); teststrs[3] = ""; teststrs[4] = "And goodbye"; final CharArrayBuffer chbuffer = new CharArrayBuffer(32); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 16); for (final String teststr : teststrs) { chbuffer.clear(); chbuffer.append(teststr); outbuf.writeLine(chbuffer); } //this write operation should have no effect outbuf.writeLine(null); final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); final WritableByteChannel outChannel = newChannel(outStream); outbuf.flush(outChannel); final ReadableByteChannel channel = newChannel(outStream.toByteArray()); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 16, 0); inbuf.fill(channel); for (final String teststr : teststrs) { chbuffer.clear(); inbuf.readLine(chbuffer, true); Assertions.assertEquals(teststr, chbuffer.toString()); } chbuffer.clear(); Assertions.assertFalse(inbuf.readLine(chbuffer, true)); chbuffer.clear(); Assertions.assertFalse(inbuf.readLine(chbuffer, true)); } @Test void testComplexReadWriteLine() throws Exception { final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 16); outbuf.write(ByteBuffer.wrap(new byte[] {'a', '\n'})); outbuf.write(ByteBuffer.wrap(new byte[] {'\r', '\n'})); outbuf.write(ByteBuffer.wrap(new byte[] {'\r', '\r', '\n'})); outbuf.write(ByteBuffer.wrap(new byte[] {'\n'})); final StringBuilder buffer = new StringBuilder(); for (int i = 0; i < 14; i++) { buffer.append("a"); } final String s1 = buffer.toString(); buffer.append("\r\n"); outbuf.write(ByteBuffer.wrap(buffer.toString().getBytes(StandardCharsets.US_ASCII))); buffer.setLength(0); for (int i = 0; i < 15; i++) { buffer.append("a"); } final String s2 = buffer.toString(); buffer.append("\r\n"); outbuf.write(ByteBuffer.wrap(buffer.toString().getBytes(StandardCharsets.US_ASCII))); buffer.setLength(0); for (int i = 0; i < 16; i++) { buffer.append("a"); } final String s3 = buffer.toString(); buffer.append("\r\n"); outbuf.write(ByteBuffer.wrap(buffer.toString().getBytes(StandardCharsets.US_ASCII))); outbuf.write(ByteBuffer.wrap(new byte[] {'a'})); final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); final WritableByteChannel outChannel = newChannel(outStream); outbuf.flush(outChannel); final ReadableByteChannel channel = newChannel(outStream.toByteArray()); final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 16, 0); inbuf.fill(channel); final CharArrayBuffer chbuffer = new CharArrayBuffer(32); chbuffer.clear(); inbuf.readLine(chbuffer, true); Assertions.assertEquals("a", chbuffer.toString()); chbuffer.clear(); inbuf.readLine(chbuffer, true); Assertions.assertEquals("", chbuffer.toString()); chbuffer.clear(); inbuf.readLine(chbuffer, true); Assertions.assertEquals("\r", chbuffer.toString()); chbuffer.clear(); inbuf.readLine(chbuffer, true); Assertions.assertEquals("", chbuffer.toString()); chbuffer.clear(); inbuf.readLine(chbuffer, true); Assertions.assertEquals(s1, chbuffer.toString()); chbuffer.clear(); inbuf.readLine(chbuffer, true); Assertions.assertEquals(s2, chbuffer.toString()); chbuffer.clear(); inbuf.readLine(chbuffer, true); Assertions.assertEquals(s3, chbuffer.toString()); chbuffer.clear(); inbuf.readLine(chbuffer, true); Assertions.assertEquals("a", chbuffer.toString()); chbuffer.clear(); inbuf.readLine(chbuffer, true); Assertions.assertFalse(inbuf.readLine(chbuffer, true)); chbuffer.clear(); inbuf.readLine(chbuffer, true); Assertions.assertFalse(inbuf.readLine(chbuffer, true)); } @Test void testReadOneByte() throws Exception { // make the buffer larger than that of transmitter final byte[] out = new byte[40]; for (int i = 0; i < out.length; i++) { out[i] = (byte)('0' + i); } final ReadableByteChannel channel = newChannel(out); final SessionInputBuffer inbuf = new SessionInputBufferImpl(16, 16, 0); while (inbuf.fill(channel) > 0) { } final byte[] in = new byte[40]; for (int i = 0; i < in.length; i++) { in[i] = (byte)inbuf.read(); } for (int i = 0; i < out.length; i++) { Assertions.assertEquals(out[i], in[i]); } } @Test void testReadByteBuffer() throws Exception { final byte[] pattern = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); final ReadableByteChannel channel = newChannel(pattern); final SessionInputBuffer inbuf = new SessionInputBufferImpl(4096, 1024, 0); while (inbuf.fill(channel) > 0) { } final ByteBuffer dst = ByteBuffer.allocate(10); Assertions.assertEquals(10, inbuf.read(dst)); dst.flip(); Assertions.assertEquals(dst, ByteBuffer.wrap(pattern, 0, 10)); dst.clear(); Assertions.assertEquals(6, inbuf.read(dst)); dst.flip(); Assertions.assertEquals(dst, ByteBuffer.wrap(pattern, 10, 6)); } @Test void testReadByteBufferWithMaxLen() throws Exception { final byte[] pattern = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); final ReadableByteChannel channel = newChannel(pattern); final SessionInputBuffer inbuf = new SessionInputBufferImpl(4096, 1024, 0); while (inbuf.fill(channel) > 0) { } final ByteBuffer dst = ByteBuffer.allocate(16); Assertions.assertEquals(10, inbuf.read(dst, 10)); dst.flip(); Assertions.assertEquals(dst, ByteBuffer.wrap(pattern, 0, 10)); dst.clear(); Assertions.assertEquals(3, inbuf.read(dst, 3)); dst.flip(); Assertions.assertEquals(dst, ByteBuffer.wrap(pattern, 10, 3)); Assertions.assertEquals(3, inbuf.read(dst, 20)); dst.flip(); Assertions.assertEquals(dst, ByteBuffer.wrap(pattern, 13, 3)); } @Test void testReadToChannel() throws Exception { final byte[] pattern = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); final ReadableByteChannel channel = newChannel(pattern); final SessionInputBuffer inbuf = new SessionInputBufferImpl(4096, 1024, 0); while (inbuf.fill(channel) > 0) { } final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); final WritableByteChannel dst = newChannel(outStream); Assertions.assertEquals(16, inbuf.read(dst)); Assertions.assertEquals(ByteBuffer.wrap(pattern), ByteBuffer.wrap(outStream.toByteArray())); } @Test void testReadToChannelWithMaxLen() throws Exception { final byte[] pattern = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); final ReadableByteChannel channel = newChannel(pattern); final SessionInputBuffer inbuf = new SessionInputBufferImpl(4096, 1024, 0); while (inbuf.fill(channel) > 0) { } final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); final WritableByteChannel dst = newChannel(outStream); Assertions.assertEquals(10, inbuf.read(dst, 10)); Assertions.assertEquals(3, inbuf.read(dst, 3)); Assertions.assertEquals(3, inbuf.read(dst, 10)); Assertions.assertEquals(ByteBuffer.wrap(pattern), ByteBuffer.wrap(outStream.toByteArray())); } @Test void testWriteByteBuffer() throws Exception { final byte[] pattern = "0123456789ABCDEF0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(4096, 1024); final ReadableByteChannel src = newChannel(pattern); outbuf.write(src); final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); final WritableByteChannel channel = newChannel(outStream); while (outbuf.flush(channel) > 0) { } Assertions.assertEquals(ByteBuffer.wrap(pattern), ByteBuffer.wrap(outStream.toByteArray())); } @Test void testWriteFromChannel() throws Exception { final byte[] pattern = "0123456789ABCDEF0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(4096, 1024); outbuf.write(ByteBuffer.wrap(pattern, 0, 16)); outbuf.write(ByteBuffer.wrap(pattern, 16, 10)); outbuf.write(ByteBuffer.wrap(pattern, 26, 6)); final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); final WritableByteChannel channel = newChannel(outStream); while (outbuf.flush(channel) > 0) { } Assertions.assertEquals(ByteBuffer.wrap(pattern), ByteBuffer.wrap(outStream.toByteArray())); } static final int SWISS_GERMAN_HELLO [] = { 0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4 }; static final int RUSSIAN_HELLO [] = { 0x412, 0x441, 0x435, 0x43C, 0x5F, 0x43F, 0x440, 0x438, 0x432, 0x435, 0x442 }; private static String constructString(final int [] unicodeChars) { final StringBuilder buffer = new StringBuilder(); if (unicodeChars != null) { for (final int unicodeChar : unicodeChars) { buffer.append((char)unicodeChar); } } return buffer.toString(); } @Test void testMultibyteCodedReadWriteLine() throws Exception { final String s1 = constructString(SWISS_GERMAN_HELLO); final String s2 = constructString(RUSSIAN_HELLO); final String s3 = "Like hello and stuff"; final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 16, StandardCharsets.UTF_8.newEncoder()); final CharArrayBuffer chbuffer = new CharArrayBuffer(32); for (int i = 0; i < 10; i++) { chbuffer.clear(); chbuffer.append(s1); outbuf.writeLine(chbuffer); chbuffer.clear(); chbuffer.append(s2); outbuf.writeLine(chbuffer); chbuffer.clear(); chbuffer.append(s3); outbuf.writeLine(chbuffer); } final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); final WritableByteChannel outChannel = newChannel(outStream); outbuf.flush(outChannel); final byte[] tmp = outStream.toByteArray(); final ReadableByteChannel channel = newChannel(tmp); final SessionInputBuffer inbuf = new SessionInputBufferImpl(16, 16, 0, StandardCharsets.UTF_8.newDecoder()); while (inbuf.fill(channel) > 0) { } for (int i = 0; i < 10; i++) { chbuffer.clear(); inbuf.readLine(chbuffer, true); Assertions.assertEquals(s1, chbuffer.toString()); chbuffer.clear(); inbuf.readLine(chbuffer, true); Assertions.assertEquals(s2, chbuffer.toString()); chbuffer.clear(); inbuf.readLine(chbuffer, true); Assertions.assertEquals(s3, chbuffer.toString()); } } @Test void testInputMatchesBufferLength() { final String s1 = "abcde"; final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 5); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); chbuffer.append(s1); Assertions.assertDoesNotThrow(() -> outbuf.writeLine(chbuffer)); } @Test void testMalformedInputActionReport() throws Exception { final String s = constructString(SWISS_GERMAN_HELLO); final byte[] tmp = s.getBytes(StandardCharsets.ISO_8859_1); final CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); decoder.onMalformedInput(CodingErrorAction.REPORT); decoder.onUnmappableCharacter(CodingErrorAction.IGNORE); final SessionInputBuffer inbuf = new SessionInputBufferImpl(16, 16, 0, decoder); final ReadableByteChannel channel = newChannel(tmp); while (inbuf.fill(channel) > 0) { } final CharArrayBuffer chbuffer = new CharArrayBuffer(16); Assertions.assertThrows(CharacterCodingException.class, () -> inbuf.readLine(chbuffer, true)); } @Test void testMalformedInputActionIgnore() throws Exception { final String s = constructString(SWISS_GERMAN_HELLO); final byte[] tmp = s.getBytes(StandardCharsets.ISO_8859_1); final CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); decoder.onMalformedInput(CodingErrorAction.IGNORE); decoder.onUnmappableCharacter(CodingErrorAction.IGNORE); final SessionInputBuffer inbuf = new SessionInputBufferImpl(16, 16, 0, decoder); final ReadableByteChannel channel = newChannel(tmp); while (inbuf.fill(channel) > 0) { } final CharArrayBuffer chbuffer = new CharArrayBuffer(16); inbuf.readLine(chbuffer, true); Assertions.assertEquals("Grezi_zm", chbuffer.toString()); } @Test void testMalformedInputActionReplace() throws Exception { final String s = constructString(SWISS_GERMAN_HELLO); final byte[] tmp = s.getBytes(StandardCharsets.ISO_8859_1); final CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); decoder.onMalformedInput(CodingErrorAction.REPLACE); decoder.onUnmappableCharacter(CodingErrorAction.IGNORE); final SessionInputBuffer inbuf = new SessionInputBufferImpl(16, 16, 0, decoder); final ReadableByteChannel channel = newChannel(tmp); while (inbuf.fill(channel) > 0) { } final CharArrayBuffer chbuffer = new CharArrayBuffer(16); inbuf.readLine(chbuffer, true); Assertions.assertEquals("Gr\ufffdezi_z\ufffdm\ufffd", chbuffer.toString()); } @Test void testUnmappableInputActionReport() { final String s = "This text contains a circumflex \u0302!!!"; final CharsetEncoder encoder = StandardCharsets.ISO_8859_1.newEncoder(); encoder.onMalformedInput(CodingErrorAction.IGNORE); encoder.onUnmappableCharacter(CodingErrorAction.REPORT); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 16, encoder); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); chbuffer.append(s); Assertions.assertThrows(CharacterCodingException.class, () -> outbuf.writeLine(chbuffer)); } @Test void testUnmappableInputActionIgnore() throws Exception { final String s = "This text contains a circumflex \u0302!!!"; final CharsetEncoder encoder = StandardCharsets.ISO_8859_1.newEncoder(); encoder.onMalformedInput(CodingErrorAction.IGNORE); encoder.onUnmappableCharacter(CodingErrorAction.IGNORE); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 16, encoder); final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final WritableByteChannel channel = newChannel(baos); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); chbuffer.append(s); outbuf.writeLine(chbuffer); outbuf.flush(channel); final String result = new String(baos.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("This text contains a circumflex !!!\r\n", result); } @Test void testUnmappableInputActionReplace() throws Exception { final String s = "This text contains a circumflex \u0302 !!!"; final CharsetEncoder encoder = StandardCharsets.ISO_8859_1.newEncoder(); encoder.onMalformedInput(CodingErrorAction.IGNORE); encoder.onUnmappableCharacter(CodingErrorAction.REPLACE); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 16, encoder); final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final WritableByteChannel channel = newChannel(baos); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); chbuffer.append(s); outbuf.writeLine(chbuffer); outbuf.flush(channel); final String result = new String(baos.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("This text contains a circumflex ? !!!\r\n", result); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/routing/0040755 0000000 0000000 00000000000 00000250600 023220 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/impl/routing/TestPathPatternMatcher.java0100644 0000000 0000000 00000005127 00000250600 030463 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.routing; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestPathPatternMatcher { @Test void testPathMatching() { final PathPatternMatcher matcher = PathPatternMatcher.INSTANCE; Assertions.assertTrue(matcher.match("/*", "/foo/request")); Assertions.assertTrue(matcher.match("/foo/*", "/foo/request")); Assertions.assertTrue(matcher.match("/foo/req*", "/foo/request")); Assertions.assertTrue(matcher.match("/foo/request", "/foo/request")); Assertions.assertFalse(matcher.match("/foo/request", "/foo/requesta")); Assertions.assertFalse(matcher.match("/foo/*", "foo/request")); Assertions.assertFalse(matcher.match("/foo/*", "/bar/foo")); Assertions.assertTrue(matcher.match("*/action.do", "/xxxxx/action.do")); Assertions.assertTrue(matcher.match("*/foo/action.do", "/xxxxx/foo/action.do")); Assertions.assertFalse(matcher.match("*.do", "foo.dont")); } @Test void testBetterMatch() { final PathPatternMatcher matcher = PathPatternMatcher.INSTANCE; Assertions.assertTrue(matcher.isBetter("/a*", "/*")); Assertions.assertTrue(matcher.isBetter("/a*", "*")); Assertions.assertTrue(matcher.isBetter("/*", "*")); Assertions.assertTrue(matcher.isBetter("/a/b*", "/a*")); Assertions.assertTrue(matcher.isBetter("/a/*", "/a/b")); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/routing/TestRequestRouter.java0100644 0000000 0000000 00000017100 00000250600 027550 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.routing; import org.apache.hc.core5.http.MisdirectedRequestException; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.UriPatternType; import org.apache.hc.core5.http.support.BasicRequestBuilder; import org.apache.hc.core5.net.URIAuthority; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestRequestRouter { @Test void testRequestRouting() throws Exception { final RequestRouter requestRouter = RequestRouter.builder(UriPatternType.URI_PATTERN) .addRoute("somehost.somedomain", "/*", 0L) .addRoute("someotherhost.somedomain", "/foo/*", 1L) .addRoute("somehost.somedomain", "/foo/*", 2L) .addRoute("somehost.somedomain", "/bar/*", 3L) .addRoute("somehost.somedomain", "/stuff", 4L) .build(); final HttpCoreContext context = HttpCoreContext.create(); Assertions.assertEquals(1L, requestRouter.resolve( BasicRequestBuilder.get("http://someotherhost.somedomain/foo/blah").build(), context)); Assertions.assertEquals(2L, requestRouter.resolve( BasicRequestBuilder.get("http://somehost.somedomain/foo/blah").build(), context)); Assertions.assertEquals(3L, requestRouter.resolve( BasicRequestBuilder.get("http://somehost.somedomain/bar/blah").build(), context)); Assertions.assertEquals(4L, requestRouter.resolve( BasicRequestBuilder.get("http://somehost.somedomain/stuff").build(), context)); Assertions.assertEquals(4L, requestRouter.resolve( BasicRequestBuilder.get("http://somehost.somedomain/stuff?huh").build(), context)); Assertions.assertEquals(0L, requestRouter.resolve( BasicRequestBuilder.get("http://somehost.somedomain/stuffed").build(), context)); Assertions.assertNull(requestRouter.resolve( BasicRequestBuilder.get("http://someotherhost.somedomain/stuff").build(), context)); Assertions.assertThrows(MisdirectedRequestException.class, () -> requestRouter.resolve( BasicRequestBuilder.get("http://somehere.in.pampa/stuff").build(), context)); } @Test void testDefaultAuthorityResolution() throws Exception { final RequestRouter requestRouter = RequestRouter.builder(UriPatternType.URI_PATTERN) .addRoute(new URIAuthority("somehost", -1), "/*", 0L) .addRoute(new URIAuthority("somehost", 80), "/*", 1L) .addRoute(new URIAuthority("somehost", 8080), "/*", 2L) .addRoute(new URIAuthority("someotherhost", 80), "/*", 10L) .build(); final HttpCoreContext context = HttpCoreContext.create(); Assertions.assertEquals(0L, requestRouter.resolve( BasicRequestBuilder.get("http://somehost/blah").build(), context)); Assertions.assertEquals(10L, requestRouter.resolve( BasicRequestBuilder.get("http://someotherhost:80/blah").build(), context)); Assertions.assertEquals(1L, requestRouter.resolve( BasicRequestBuilder.get("http://somehost:80/blah").build(), context)); Assertions.assertEquals(2L, requestRouter.resolve( BasicRequestBuilder.get("http://somehost:8080/blah").build(), context)); Assertions.assertThrows(MisdirectedRequestException.class, () -> requestRouter.resolve( BasicRequestBuilder.get("http://somehere.in.pampa/stuff").build(), context)); } @Test void testCustomAuthorityResolution() throws Exception { final RequestRouter requestRouter = RequestRouter.builder(UriPatternType.URI_PATTERN) .addRoute(new URIAuthority("somehost", -1), "/*", 1L) .addRoute(new URIAuthority("someotherhost", -1), "/*", 2L) .resolveAuthority((scheme, authority) -> authority != null ? new URIAuthority(authority.getHostName(), -1) : new URIAuthority("somehost")) .build(); final HttpCoreContext context = HttpCoreContext.create(); Assertions.assertEquals(1L, requestRouter.resolve( BasicRequestBuilder.get("http://somehost/blah").build(), context)); Assertions.assertEquals(2L, requestRouter.resolve( BasicRequestBuilder.get("http://someotherhost:80/blah").build(), context)); Assertions.assertEquals(1L, requestRouter.resolve( BasicRequestBuilder.get("http://somehost:80/blah").build(), context)); Assertions.assertEquals(1L, requestRouter.resolve( BasicRequestBuilder.get("http://somehost:8080/blah").build(), context)); Assertions.assertEquals(1L, requestRouter.resolve( BasicRequestBuilder.get("/blah").build(), context)); Assertions.assertThrows(MisdirectedRequestException.class, () -> requestRouter.resolve( BasicRequestBuilder.get("http://somehere.in.pampa/stuff").build(), context)); } @Test void testDownstreamResolution() throws Exception { final RequestRouter requestRouter = RequestRouter.builder(UriPatternType.URI_PATTERN) .addRoute(new URIAuthority("somehost", 80), "/*", 1L) .addRoute(new URIAuthority("someotherhost", 80), "/*", 10L) .resolveAuthority((scheme, authority) -> authority) .downstream((request, context) -> -1L) .build(); final HttpCoreContext context = HttpCoreContext.create(); Assertions.assertEquals(-1L, requestRouter.resolve( BasicRequestBuilder.get("http://somehost/blah").build(), context)); Assertions.assertEquals(10L, requestRouter.resolve( BasicRequestBuilder.get("http://someotherhost:80/blah").build(), context)); Assertions.assertEquals(1L, requestRouter.resolve( BasicRequestBuilder.get("http://somehost:80/blah").build(), context)); Assertions.assertEquals(-1L, requestRouter.resolve( BasicRequestBuilder.get("http://somehost:8080/blah").build(), context)); Assertions.assertEquals(-1L, requestRouter.resolve( BasicRequestBuilder.get("/blah").build(), context)); Assertions.assertEquals(-1L, requestRouter.resolve( BasicRequestBuilder.get("http://somehere.in.pampa/stuff").build(), context)); } } httpcore5/src/test/java/org/apache/hc/core5/http/impl/routing/TestUriPathRouter.java0100644 0000000 0000000 00000020240 00000250600 027473 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.impl.routing; import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestUriPathRouter { @Test void testBestMatchWildCardMatching() { final UriPathRouter.BestMatcher matcher = UriPathRouter.BestMatcher.getInstance(); final List> routes = Arrays.asList( new PathRoute<>("*", 0L), new PathRoute<>("/one/*", 1L), new PathRoute<>("/one/two/*", 2L), new PathRoute<>("/one/two/three/*", 3L), new PathRoute<>("*.view", 4L), new PathRoute<>("*.form", 5L)); Assertions.assertEquals(1L, matcher.apply("/one/request", routes)); Assertions.assertEquals(2L, matcher.apply("/one/two/request", routes)); Assertions.assertEquals(3L, matcher.apply("/one/two/three/request", routes)); Assertions.assertEquals(0L, matcher.apply("default/request", routes)); Assertions.assertEquals(4L, matcher.apply("that.view", routes)); Assertions.assertEquals(5L, matcher.apply("that.form", routes)); Assertions.assertEquals(0L, matcher.apply("whatever", routes)); } @Test void testBestMatchWildCardMatchingSuffixPrefixPrecedence() { final UriPathRouter.BestMatcher matcher = UriPathRouter.BestMatcher.getInstance(); final List> routes = Arrays.asList( new PathRoute<>("/ma*", 1L), new PathRoute<>("*tch", 2L)); Assertions.assertEquals(1L, matcher.apply("/match", routes)); } @Test void testBestMatchWildCardMatchingExactMatch() { final UriPathRouter.BestMatcher matcher = UriPathRouter.BestMatcher.getInstance(); final List> routes = Arrays.asList( new PathRoute<>("exact", 1L), new PathRoute<>("*", 0L)); Assertions.assertEquals(1L, matcher.apply("exact", routes)); } @Test void testBestMatchWildCardMatchingNoMatch() { final UriPathRouter.BestMatcher matcher = UriPathRouter.BestMatcher.getInstance(); final List> routes = Arrays.asList( new PathRoute<>("/this/*", 1L), new PathRoute<>("/that/*", 2L)); Assertions.assertNull(matcher.apply("huh?", routes)); } @Test void testOrderedWildCardMatching1() { final UriPathRouter.OrderedMatcher matcher = UriPathRouter.OrderedMatcher.getInstance(); final List> routes = Arrays.asList( new PathRoute<>("*", 0L), new PathRoute<>("/one/*", 1L), new PathRoute<>("/one/two/*", 2L), new PathRoute<>("/one/two/three/*", 3L), new PathRoute<>("*.view", 4L), new PathRoute<>("*.form", 5L)); Assertions.assertEquals(0L, matcher.apply("/one/request", routes)); Assertions.assertEquals(0L, matcher.apply("/one/two/request", routes)); Assertions.assertEquals(0L, matcher.apply("/one/two/three/request", routes)); Assertions.assertEquals(0L, matcher.apply("default/request", routes)); Assertions.assertEquals(0L, matcher.apply("that.view", routes)); Assertions.assertEquals(0L, matcher.apply("that.form", routes)); Assertions.assertEquals(0L, matcher.apply("whatever", routes)); final List> routes2 = Arrays.asList( new PathRoute<>("/one/two/three/*", 3L), new PathRoute<>("/one/two/*", 2L), new PathRoute<>("/one/*", 1L), new PathRoute<>("*.view", 4L), new PathRoute<>("*.form", 5L), new PathRoute<>("*", 0L)); Assertions.assertEquals(3L, matcher.apply("/one/two/three/request", routes2)); Assertions.assertEquals(2L, matcher.apply("/one/two/request", routes2)); Assertions.assertEquals(1L, matcher.apply("/one/request", routes2)); Assertions.assertEquals(0L, matcher.apply("default/request", routes2)); Assertions.assertEquals(4L, matcher.apply("that.view", routes2)); Assertions.assertEquals(5L, matcher.apply("that.form", routes2)); Assertions.assertEquals(0L, matcher.apply("whatever", routes2)); } @Test void testOrderedWildCardMatchingSuffixPrefixPrecedence() { final UriPathRouter.OrderedMatcher matcher = UriPathRouter.OrderedMatcher.getInstance(); final List> routes = Arrays.asList( new PathRoute<>("/ma*", 1L), new PathRoute<>("*tch", 2L)); Assertions.assertEquals(1L, matcher.apply("/match", routes)); } @Test void testOrderedStarAndExact() { final UriPathRouter.OrderedMatcher matcher = UriPathRouter.OrderedMatcher.getInstance(); final List> routes = Arrays.asList( new PathRoute<>("*", 0L), new PathRoute<>("exact", 1L)); Assertions.assertEquals(0L, matcher.apply("exact", routes)); } @Test void testOrderedExactAndStar() { final UriPathRouter.OrderedMatcher matcher = UriPathRouter.OrderedMatcher.getInstance(); final List> routes = Arrays.asList( new PathRoute<>("exact", 1L), new PathRoute<>("*", 0L)); Assertions.assertEquals(1L, matcher.apply("exact", routes)); } @Test void testRegExMatching() { final UriPathRouter.RegexMatcher matcher = UriPathRouter.RegexMatcher.getInstance(); final List> routes = Arrays.asList( new PathRoute<>(Pattern.compile("/one/two/three/.*"), 3L), new PathRoute<>(Pattern.compile("/one/two/.*"), 2L), new PathRoute<>(Pattern.compile("/one/.*"), 1L), new PathRoute<>(Pattern.compile(".*\\.view"), 4L), new PathRoute<>(Pattern.compile(".*\\.form"), 5L), new PathRoute<>(Pattern.compile(".*"), 0L)); Assertions.assertEquals(1L, matcher.apply("/one/request", routes)); Assertions.assertEquals(2L, matcher.apply("/one/two/request", routes)); Assertions.assertEquals(3L, matcher.apply("/one/two/three/request", routes)); Assertions.assertEquals(4L, matcher.apply("/that.view", routes)); Assertions.assertEquals(5L, matcher.apply("/that.form", routes)); Assertions.assertEquals(0L, matcher.apply("/default/request", routes)); } @Test void testRegExWildCardMatchingSuffixPrefixPrecedence() { final UriPathRouter.RegexMatcher matcher = UriPathRouter.RegexMatcher.getInstance(); final List> routes = Arrays.asList( new PathRoute<>(Pattern.compile("/ma.*"), 1L), new PathRoute<>(Pattern.compile(".*tch"), 2L)); Assertions.assertEquals(1L, matcher.apply("/match", routes)); } } httpcore5/src/test/java/org/apache/hc/core5/http/io/0040755 0000000 0000000 00000000000 00000250600 021177 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/io/TestEofSensorInputStream.java0100644 0000000 0000000 00000015124 00000250600 027001 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io; import java.io.IOException; import java.io.InputStream; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @SuppressWarnings({"boxing","static-access"}) // test code class TestEofSensorInputStream { private InputStream inStream; private EofSensorWatcher eofwatcher; private EofSensorInputStream eofstream; @BeforeEach void setup() { inStream = Mockito.mock(InputStream.class); eofwatcher = Mockito.mock(EofSensorWatcher.class); eofstream = new EofSensorInputStream(inStream, eofwatcher); } @Test void testClose() throws Exception { Mockito.when(eofwatcher.streamClosed(Mockito.any())).thenReturn(Boolean.TRUE); eofstream.close(); Assertions.assertTrue(eofstream.isSelfClosed()); Assertions.assertNull(eofstream.getWrappedStream()); Mockito.verify(inStream, Mockito.times(1)).close(); Mockito.verify(eofwatcher).streamClosed(inStream); eofstream.close(); } @Test void testCloseIOError() throws Exception { Mockito.when(eofwatcher.streamClosed(Mockito.any())).thenThrow(new IOException()); Assertions.assertThrows(IOException.class, () -> eofstream.close()); Assertions.assertTrue(eofstream.isSelfClosed()); Assertions.assertNull(eofstream.getWrappedStream()); Mockito.verify(eofwatcher).streamClosed(inStream); } @Test void testAbortConnection() throws Exception { Mockito.when(eofwatcher.streamAbort(Mockito.any())).thenReturn(Boolean.TRUE); eofstream.abort(); Assertions.assertTrue(eofstream.isSelfClosed()); Assertions.assertNull(eofstream.getWrappedStream()); Mockito.verify(inStream, Mockito.times(1)).close(); Mockito.verify(eofwatcher).streamAbort(inStream); eofstream.abort(); } @Test void testAbortConnectionIOError() throws Exception { Mockito.when(eofwatcher.streamAbort(Mockito.any())).thenThrow(new IOException()); Assertions.assertThrows(IOException.class, () -> eofstream.abort()); Assertions.assertTrue(eofstream.isSelfClosed()); Assertions.assertNull(eofstream.getWrappedStream()); Mockito.verify(eofwatcher).streamAbort(inStream); } @Test void testRead() throws Exception { Mockito.when(eofwatcher.eofDetected(Mockito.any())).thenReturn(Boolean.TRUE); Mockito.when(inStream.read()).thenReturn(0, -1); Assertions.assertEquals(0, eofstream.read()); Assertions.assertFalse(eofstream.isSelfClosed()); Assertions.assertNotNull(eofstream.getWrappedStream()); Mockito.verify(eofwatcher, Mockito.never()).eofDetected(inStream); Assertions.assertEquals(-1, eofstream.read()); Assertions.assertFalse(eofstream.isSelfClosed()); Assertions.assertNull(eofstream.getWrappedStream()); Mockito.verify(inStream, Mockito.times(1)).close(); Mockito.verify(eofwatcher).eofDetected(inStream); Assertions.assertEquals(-1, eofstream.read()); } @Test void testReadIOError() throws Exception { Mockito.when(eofwatcher.eofDetected(Mockito.any())).thenReturn(Boolean.TRUE); Mockito.when(inStream.read()).thenThrow(new IOException()); Assertions.assertThrows(IOException.class, () -> eofstream.read()); Assertions.assertFalse(eofstream.isSelfClosed()); Assertions.assertNull(eofstream.getWrappedStream()); Mockito.verify(eofwatcher).streamAbort(inStream); } @Test void testReadByteArray() throws Exception { Mockito.when(eofwatcher.eofDetected(Mockito.any())).thenReturn(Boolean.TRUE); Mockito.when(inStream.read(Mockito.any(), Mockito.anyInt(), Mockito.anyInt())) .thenReturn(1, -1); final byte[] tmp = new byte[1]; Assertions.assertEquals(1, eofstream.read(tmp)); Assertions.assertFalse(eofstream.isSelfClosed()); Assertions.assertNotNull(eofstream.getWrappedStream()); Mockito.verify(eofwatcher, Mockito.never()).eofDetected(inStream); Assertions.assertEquals(-1, eofstream.read(tmp)); Assertions.assertFalse(eofstream.isSelfClosed()); Assertions.assertNull(eofstream.getWrappedStream()); Mockito.verify(inStream, Mockito.times(1)).close(); Mockito.verify(eofwatcher).eofDetected(inStream); Assertions.assertEquals(-1, eofstream.read(tmp)); } @Test void testReadByteArrayIOError() throws Exception { Mockito.when(eofwatcher.eofDetected(Mockito.any())).thenReturn(Boolean.TRUE); Mockito.when(inStream.read(Mockito.any(), Mockito.anyInt(), Mockito.anyInt())) .thenThrow(new IOException()); final byte[] tmp = new byte[1]; Assertions.assertThrows(IOException.class, () -> eofstream.read(tmp)); Assertions.assertFalse(eofstream.isSelfClosed()); Assertions.assertNull(eofstream.getWrappedStream()); Mockito.verify(eofwatcher).streamAbort(inStream); } @Test void testReadAfterAbort() throws Exception { Mockito.when(eofwatcher.streamAbort(Mockito.any())).thenReturn(Boolean.TRUE); eofstream.abort(); Assertions.assertThrows(IOException.class, () -> eofstream.read()); final byte[] tmp = new byte[1]; Assertions.assertThrows(IOException.class, () -> eofstream.read(tmp)); } } httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/0040755 0000000 0000000 00000000000 00000250600 022513 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestBasicHttpEntity.java0100644 0000000 0000000 00000012065 00000250600 027275 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ContentType; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link BasicHttpEntity}. * */ class TestBasicHttpEntity { @Test void testBasics() { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); final BasicHttpEntity httpentity = new BasicHttpEntity(new ByteArrayInputStream(bytes), bytes.length, null); Assertions.assertEquals(bytes.length, httpentity.getContentLength()); // always false Assertions.assertFalse(httpentity.isRepeatable()); // always true Assertions.assertTrue(httpentity.isStreaming()); } @Test void testConstructorNullContent() { // BasicHttpEntity(InputStream, ContentType) Assertions.assertThrows(NullPointerException.class, () -> new BasicHttpEntity(null, ContentType.APPLICATION_ATOM_XML)); Assertions.assertThrows(NullPointerException.class, () -> new BasicHttpEntity(null, null)); // BasicHttpEntity(InputStream, ContentType, boolean) Assertions.assertThrows(NullPointerException.class, () -> new BasicHttpEntity(null, ContentType.APPLICATION_ATOM_XML, false)); Assertions.assertThrows(NullPointerException.class, () -> new BasicHttpEntity(null, null, false)); // BasicHttpEntity(InputStream, ContentType, String) Assertions.assertThrows(NullPointerException.class, () -> new BasicHttpEntity(null, ContentType.APPLICATION_ATOM_XML, "")); Assertions.assertThrows(NullPointerException.class, () -> new BasicHttpEntity(null, null, "")); // BasicHttpEntity(InputStream, long, ContentType) Assertions.assertThrows(NullPointerException.class, () -> new BasicHttpEntity(null, 0, ContentType.APPLICATION_ATOM_XML)); Assertions.assertThrows(NullPointerException.class, () -> new BasicHttpEntity(null, 0, null)); // BasicHttpEntity(InputStream, long, ContentType, String) Assertions.assertThrows(NullPointerException.class, () -> new BasicHttpEntity(null, 0, ContentType.APPLICATION_ATOM_XML, "")); Assertions.assertThrows(NullPointerException.class, () -> new BasicHttpEntity(null, 0, null, "")); // BasicHttpEntity(InputStream, long, ContentType, String, boolean) Assertions.assertThrows(NullPointerException.class, () -> new BasicHttpEntity(null, 0, ContentType.APPLICATION_ATOM_XML, "", false)); Assertions.assertThrows(NullPointerException.class, () -> new BasicHttpEntity(null, 0, null, "", false)); } @Test void testToString() throws Exception { try (final BasicHttpEntity httpentity = new BasicHttpEntity(EmptyInputStream.INSTANCE, 10, ContentType.parseLenient("blah"), "yada", true)) { Assertions.assertEquals( "[Entity-Class: BasicHttpEntity, Content-Type: blah, Content-Encoding: yada, chunked: true]", httpentity.toString()); } } @Test void testWriteTo() throws Exception { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); final BasicHttpEntity httpentity = new BasicHttpEntity(new ByteArrayInputStream(bytes), bytes.length, ContentType.TEXT_PLAIN); // always false Assertions.assertFalse(httpentity.isRepeatable()); // always true Assertions.assertTrue(httpentity.isStreaming()); final ByteArrayOutputStream out = new ByteArrayOutputStream(); httpentity.writeTo(out); final byte[] bytes2 = out.toByteArray(); Assertions.assertNotNull(bytes2); Assertions.assertEquals(bytes.length, bytes2.length); for (int i = 0; i < bytes.length; i++) { Assertions.assertEquals(bytes[i], bytes2[i]); } } } httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestBufferedHttpEntity.java0100644 0000000 0000000 00000012647 00000250600 030004 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link BufferedHttpEntity}. * */ class TestBufferedHttpEntity { @Test void testBufferingEntity() throws Exception { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); final BufferedHttpEntity entity = new BufferedHttpEntity( new InputStreamEntity(new ByteArrayInputStream(bytes), -1, null)); Assertions.assertEquals(bytes.length, entity.getContentLength()); Assertions.assertTrue(entity.isRepeatable()); Assertions.assertFalse(entity.isChunked()); Assertions.assertFalse(entity.isStreaming()); // test if we can obtain contain multiple times Assertions.assertNotNull(entity.getContent ()); Assertions.assertNotNull(entity.getContent ()); } @Test void testWrappingEntity() throws Exception { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); final ByteArrayEntity httpentity = new ByteArrayEntity(bytes, null, true); try (final BufferedHttpEntity bufentity = new BufferedHttpEntity(httpentity)) { Assertions.assertEquals(bytes.length, bufentity.getContentLength()); Assertions.assertTrue(bufentity.isRepeatable()); Assertions.assertTrue(bufentity.isChunked()); Assertions.assertFalse(bufentity.isStreaming()); // test if we can obtain contain multiple times Assertions.assertNotNull(bufentity.getContent()); Assertions.assertNotNull(bufentity.getContent()); } } @Test void testIllegalConstructor() { Assertions.assertThrows(NullPointerException.class, () -> new BufferedHttpEntity(null)); } @Test void testWriteToBuffered() throws Exception { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); final InputStreamEntity httpentity = new InputStreamEntity(new ByteArrayInputStream(bytes), -1, null); try (final BufferedHttpEntity bufentity = new BufferedHttpEntity(httpentity)) { ByteArrayOutputStream out = new ByteArrayOutputStream(); bufentity.writeTo(out); byte[] bytes2 = out.toByteArray(); Assertions.assertNotNull(bytes2); Assertions.assertEquals(bytes.length, bytes2.length); for (int i = 0; i < bytes.length; i++) { Assertions.assertEquals(bytes[i], bytes2[i]); } out = new ByteArrayOutputStream(); bufentity.writeTo(out); bytes2 = out.toByteArray(); Assertions.assertNotNull(bytes2); Assertions.assertEquals(bytes.length, bytes2.length); for (int i = 0; i < bytes.length; i++) { Assertions.assertEquals(bytes[i], bytes2[i]); } Assertions.assertThrows(NullPointerException.class, () -> bufentity.writeTo(null)); } } @Test void testWriteToWrapped() throws Exception { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); final ByteArrayEntity httpentity = new ByteArrayEntity(bytes, null); try (final BufferedHttpEntity bufentity = new BufferedHttpEntity(httpentity)) { ByteArrayOutputStream out = new ByteArrayOutputStream(); bufentity.writeTo(out); byte[] bytes2 = out.toByteArray(); Assertions.assertNotNull(bytes2); Assertions.assertEquals(bytes.length, bytes2.length); for (int i = 0; i < bytes.length; i++) { Assertions.assertEquals(bytes[i], bytes2[i]); } out = new ByteArrayOutputStream(); bufentity.writeTo(out); bytes2 = out.toByteArray(); Assertions.assertNotNull(bytes2); Assertions.assertEquals(bytes.length, bytes2.length); for (int i = 0; i < bytes.length; i++) { Assertions.assertEquals(bytes[i], bytes2[i]); } Assertions.assertThrows(NullPointerException.class, () -> bufentity.writeTo(null)); } } } httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestByteArrayEntity.java0100644 0000000 0000000 00000013102 00000250600 027307 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link ByteArrayEntity}. * */ class TestByteArrayEntity { @Test void testBasics() throws Exception { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); try (final ByteArrayEntity entity = new ByteArrayEntity(bytes, null)) { Assertions.assertEquals(bytes.length, entity.getContentLength()); Assertions.assertNotNull(entity.getContent()); Assertions.assertTrue(entity.isRepeatable()); Assertions.assertFalse(entity.isStreaming()); } } @Test void testBasicOffLen() throws Exception { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); try (final ByteArrayEntity entity = new ByteArrayEntity(bytes, 8, 7, null)) { Assertions.assertEquals(7, entity.getContentLength()); Assertions.assertNotNull(entity.getContent()); Assertions.assertTrue(entity.isRepeatable()); Assertions.assertFalse(entity.isStreaming()); } } @Test void testIllegalConstructorNullByteArray() { Assertions.assertThrows(NullPointerException.class, () -> new ByteArrayEntity(null, null)); } @Test void testIllegalConstructorBadLen() { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); Assertions.assertThrows(IllegalArgumentException.class, () -> new ByteArrayEntity(bytes, 0, bytes.length + 1, null)); } @Test void testIllegalConstructorBadOff1() { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); Assertions.assertThrows(IllegalArgumentException.class, () -> new ByteArrayEntity(bytes, -1, bytes.length, null)); } @Test void testIllegalConstructorBadOff2() { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); Assertions.assertThrows(IllegalArgumentException.class, () -> new ByteArrayEntity(bytes, bytes.length + 1, bytes.length, null)); } @Test void testWriteTo() throws Exception { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); try (final ByteArrayEntity entity = new ByteArrayEntity(bytes, null)) { ByteArrayOutputStream out = new ByteArrayOutputStream(); entity.writeTo(out); byte[] bytes2 = out.toByteArray(); Assertions.assertNotNull(bytes2); Assertions.assertEquals(bytes.length, bytes2.length); for (int i = 0; i < bytes.length; i++) { Assertions.assertEquals(bytes[i], bytes2[i]); } out = new ByteArrayOutputStream(); entity.writeTo(out); bytes2 = out.toByteArray(); Assertions.assertNotNull(bytes2); Assertions.assertEquals(bytes.length, bytes2.length); for (int i = 0; i < bytes.length; i++) { Assertions.assertEquals(bytes[i], bytes2[i]); } Assertions.assertThrows(NullPointerException.class, () -> entity.writeTo(null)); } } @Test void testWriteToOffLen() throws Exception { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); final int off = 8; final int len = 7; try (final ByteArrayEntity entity = new ByteArrayEntity(bytes, off, len, null)) { ByteArrayOutputStream out = new ByteArrayOutputStream(); entity.writeTo(out); byte[] bytes2 = out.toByteArray(); Assertions.assertNotNull(bytes2); Assertions.assertEquals(len, bytes2.length); for (int i = 0; i < len; i++) { Assertions.assertEquals(bytes[i + off], bytes2[i]); } out = new ByteArrayOutputStream(); entity.writeTo(out); bytes2 = out.toByteArray(); Assertions.assertNotNull(bytes2); Assertions.assertEquals(len, bytes2.length); for (int i = 0; i < len; i++) { Assertions.assertEquals(bytes[i + off], bytes2[i]); } Assertions.assertThrows(NullPointerException.class, () -> entity.writeTo(null)); } } } httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestByteBufferEntity.java0100644 0000000 0000000 00000006247 00000250600 027456 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link ByteBufferEntity}. * */ class TestByteBufferEntity { @Test void testBasics() throws Exception { final ByteBuffer bytes = ByteBuffer.wrap("Message content".getBytes(StandardCharsets.US_ASCII)); try (final ByteBufferEntity httpentity = new ByteBufferEntity(bytes, null)) { Assertions.assertEquals(bytes.capacity(), httpentity.getContentLength()); Assertions.assertNotNull(httpentity.getContent()); Assertions.assertFalse(httpentity.isRepeatable()); Assertions.assertFalse(httpentity.isStreaming()); } } @Test void testWriteTo() throws Exception { final ByteBuffer bytes = ByteBuffer.wrap("Message content".getBytes(StandardCharsets.US_ASCII)); try (final ByteBufferEntity httpentity = new ByteBufferEntity(bytes, null)) { ByteArrayOutputStream out = new ByteArrayOutputStream(); httpentity.writeTo(out); byte[] bytes2 = out.toByteArray(); Assertions.assertNotNull(bytes2); Assertions.assertEquals(bytes.capacity(), bytes2.length); bytes.position(0); for (int i = 0; i < bytes2.length; i++) { Assertions.assertEquals(bytes.get(i), bytes2[i]); } out = new ByteArrayOutputStream(); httpentity.writeTo(out); bytes2 = out.toByteArray(); Assertions.assertNotNull(bytes2); Assertions.assertEquals(bytes.capacity(), bytes2.length); bytes.position(0); for (int i = 0; i < bytes.capacity(); i++) { Assertions.assertEquals(bytes.get(i), bytes2[i]); } Assertions.assertThrows(NullPointerException.class, () -> httpentity.writeTo(null)); } } } httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestEntityUtils.java0100644 0000000 0000000 00000031424 00000250600 026514 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.http.message.BasicNameValuePair; import org.apache.hc.core5.net.WWWFormCodec; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link EntityUtils}. * */ class TestEntityUtils { @Test void testNullEntityToByteArray() { Assertions.assertThrows(NullPointerException.class, () -> EntityUtils.toByteArray(null)); } @Test void testMaxIntContentToByteArray() { final byte[] content = "Message content".getBytes(StandardCharsets.US_ASCII); final BasicHttpEntity entity = new BasicHttpEntity(new ByteArrayInputStream(content), Integer.MAX_VALUE + 100L, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.US_ASCII)); Assertions.assertThrows(IllegalArgumentException.class, () -> EntityUtils.toByteArray(entity)); } @Test void testUnknownLengthContentToByteArray() throws Exception { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); final BasicHttpEntity entity = new BasicHttpEntity(new ByteArrayInputStream(bytes), -1, null); final byte[] bytes2 = EntityUtils.toByteArray(entity); Assertions.assertNotNull(bytes2); Assertions.assertEquals(bytes.length, bytes2.length); for (int i = 0; i < bytes.length; i++) { Assertions.assertEquals(bytes[i], bytes2[i]); } } @Test void testKnownLengthContentToByteArray() throws Exception { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); final BasicHttpEntity entity = new BasicHttpEntity(new ByteArrayInputStream(bytes), bytes.length, null); final byte[] bytes2 = EntityUtils.toByteArray(entity); Assertions.assertNotNull(bytes2); Assertions.assertEquals(bytes.length, bytes2.length); for (int i = 0; i < bytes.length; i++) { Assertions.assertEquals(bytes[i], bytes2[i]); } } @Test void testNullEntityToString() { Assertions.assertThrows(NullPointerException.class, () -> EntityUtils.toString(null)); } @Test void testMaxIntContentToString() { final byte[] content = "Message content".getBytes(StandardCharsets.US_ASCII); final BasicHttpEntity entity = new BasicHttpEntity(new ByteArrayInputStream(content), Integer.MAX_VALUE + 100L, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.US_ASCII)); Assertions.assertThrows(IllegalArgumentException.class, () -> EntityUtils.toString(entity, "US-ASCII")); } @Test void testUnknownLengthContentToString() throws Exception { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); final BasicHttpEntity entity = new BasicHttpEntity(new ByteArrayInputStream(bytes), -1, null); final String s = EntityUtils.toString(entity, "US-ASCII"); Assertions.assertEquals("Message content", s); } @Test void testKnownLengthContentToString() throws Exception { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); final BasicHttpEntity entity = new BasicHttpEntity(new ByteArrayInputStream(bytes), bytes.length, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.US_ASCII)); final String s = EntityUtils.toString(entity, StandardCharsets.US_ASCII); Assertions.assertEquals("Message content", s); } static final int SWISS_GERMAN_HELLO [] = { 0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4 }; static final int RUSSIAN_HELLO [] = { 0x412, 0x441, 0x435, 0x43C, 0x5F, 0x43F, 0x440, 0x438, 0x432, 0x435, 0x442 }; private static String constructString(final int [] unicodeChars) { final StringBuilder buffer = new StringBuilder(); if (unicodeChars != null) { for (final int unicodeChar : unicodeChars) { buffer.append((char)unicodeChar); } } return buffer.toString(); } @Test void testNoCharsetContentToString() { final String content = constructString(SWISS_GERMAN_HELLO); final byte[] bytes = content.getBytes(StandardCharsets.UTF_8); final BasicHttpEntity entity = new BasicHttpEntity(new ByteArrayInputStream(bytes), ContentType.TEXT_PLAIN); Assertions.assertDoesNotThrow(() -> EntityUtils.toString(entity)); } @Test void testDefaultCharsetContentToString() throws Exception { final String content = constructString(RUSSIAN_HELLO); final byte[] bytes = content.getBytes(Charset.forName("KOI8-R")); final BasicHttpEntity entity = new BasicHttpEntity(new ByteArrayInputStream(bytes), ContentType.parse("text/plain")); final String s = EntityUtils.toString(entity, Charset.forName("KOI8-R")); Assertions.assertEquals(content, s); } @Test void testContentWithContentTypeToString() throws Exception { final String content = constructString(RUSSIAN_HELLO); final byte[] bytes = content.getBytes(StandardCharsets.UTF_8); final BasicHttpEntity entity = new BasicHttpEntity(new ByteArrayInputStream(bytes), ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)); final String s = EntityUtils.toString(entity, "ISO-8859-1"); Assertions.assertEquals(content, s); } @Test void testContentWithInvalidContentTypeToString() throws Exception { final String content = constructString(RUSSIAN_HELLO); final byte[] bytes = content.getBytes(StandardCharsets.UTF_8); final HttpEntity entity = new AbstractHttpEntity("text/plain; charset=nosuchcharset", null) { @Override public InputStream getContent() throws IOException, UnsupportedOperationException { return new ByteArrayInputStream(bytes); } @Override public boolean isStreaming() { return false; } @Override public long getContentLength() { return bytes.length; } @Override public void close() throws IOException { } }; final String s = EntityUtils.toString(entity, "UTF-8"); Assertions.assertEquals(content, s); } private static void assertNameValuePair ( final NameValuePair parameter, final String expectedName, final String expectedValue) { Assertions.assertEquals(parameter.getName(), expectedName); Assertions.assertEquals(parameter.getValue(), expectedValue); } @Test void testParseEntity() throws Exception { final StringEntity entity1 = new StringEntity("Name1=Value1", ContentType.APPLICATION_FORM_URLENCODED); final List result = EntityUtils.parse(entity1); Assertions.assertEquals(1, result.size()); assertNameValuePair(result.get(0), "Name1", "Value1"); final StringEntity entity2 = new StringEntity("Name1=Value1", ContentType.parse("text/test")); Assertions.assertTrue(EntityUtils.parse(entity2).isEmpty()); } @Test void testParseUTF8Entity() throws Exception { final String ru_hello = constructString(RUSSIAN_HELLO); final String ch_hello = constructString(SWISS_GERMAN_HELLO); final List parameters = new ArrayList<>(); parameters.add(new BasicNameValuePair("russian", ru_hello)); parameters.add(new BasicNameValuePair("swiss", ch_hello)); final String s = WWWFormCodec.format(parameters, StandardCharsets.UTF_8); Assertions.assertEquals("russian=%D0%92%D1%81%D0%B5%D0%BC_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82" + "&swiss=Gr%C3%BCezi_z%C3%A4m%C3%A4", s); final StringEntity entity = new StringEntity(s, ContentType.APPLICATION_FORM_URLENCODED.withCharset(StandardCharsets.UTF_8)); final List result = EntityUtils.parse(entity); Assertions.assertEquals(2, result.size()); assertNameValuePair(result.get(0), "russian", ru_hello); assertNameValuePair(result.get(1), "swiss", ch_hello); } @Test void testByteArrayMaxResultLength() throws IOException { final byte[] allBytes = "Message content".getBytes(StandardCharsets.US_ASCII); final Map testCases = new HashMap<>(); testCases.put(0, new byte[]{}); testCases.put(2, Arrays.copyOfRange(allBytes, 0, 2)); testCases.put(allBytes.length, allBytes); testCases.put(Integer.MAX_VALUE, allBytes); for (final Map.Entry tc : testCases.entrySet()) { final BasicHttpEntity entity = new BasicHttpEntity(new ByteArrayInputStream(allBytes), allBytes.length, null); final byte[] bytes = EntityUtils.toByteArray(entity, tc.getKey()); final byte[] expectedBytes = tc.getValue(); Assertions.assertNotNull(bytes); Assertions.assertEquals(expectedBytes.length, bytes.length); for (int i = 0; i < expectedBytes.length; i++) { Assertions.assertEquals(expectedBytes[i], bytes[i]); } } } @Test void testByteArrayMaxResultLengthWithNoContentLength() throws IOException { final byte b = 'b'; final byte[] allBytes = new byte[5000]; Arrays.fill(allBytes, b); final Map testCases = new HashMap<>(); testCases.put(0, new byte[]{}); testCases.put(2, Arrays.copyOfRange(allBytes, 0, 2)); testCases.put(allBytes.length, allBytes); testCases.put(Integer.MAX_VALUE, allBytes); for (final Map.Entry tc : testCases.entrySet()) { final BasicHttpEntity entity = new BasicHttpEntity(new ByteArrayInputStream(allBytes), null); final byte[] bytes = EntityUtils.toByteArray(entity, tc.getKey()); final byte[] expectedBytes = tc.getValue(); Assertions.assertNotNull(bytes); Assertions.assertEquals(expectedBytes.length, bytes.length); for (int i = 0; i < expectedBytes.length; i++) { Assertions.assertEquals(expectedBytes[i], bytes[i]); } } } @Test void testStringMaxResultLength() throws IOException, ParseException { final String allMessage = "Message content"; final byte[] allBytes = allMessage.getBytes(StandardCharsets.US_ASCII); final Map testCases = new HashMap<>(); testCases.put(7, allMessage.substring(0, 7)); testCases.put(allMessage.length(), allMessage); testCases.put(Integer.MAX_VALUE, allMessage); for (final Map.Entry tc : testCases.entrySet()) { final BasicHttpEntity entity = new BasicHttpEntity(new ByteArrayInputStream(allBytes), allBytes.length, null); final String string = EntityUtils.toString(entity, StandardCharsets.US_ASCII, tc.getKey()); final String expectedString = tc.getValue(); Assertions.assertNotNull(string); Assertions.assertEquals(expectedString, string); } } } httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestFileEntity.java0100644 0000000 0000000 00000006575 00000250600 026304 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import org.apache.hc.core5.http.ContentType; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link FileEntity}. * */ class TestFileEntity { @Test void testBasics() throws Exception { final File tmpfile = File.createTempFile("testfile", ".txt"); tmpfile.deleteOnExit(); try (final FileEntity httpentity = new FileEntity(tmpfile, ContentType.TEXT_PLAIN)) { Assertions.assertEquals(tmpfile.length(), httpentity.getContentLength()); try (InputStream content = httpentity.getContent()) { Assertions.assertNotNull(content); } Assertions.assertTrue(httpentity.isRepeatable()); Assertions.assertFalse(httpentity.isStreaming()); Assertions.assertTrue(tmpfile.delete(), "Failed to delete " + tmpfile); } } @Test void testNullConstructor() { Assertions.assertThrows(NullPointerException.class, () -> new FileEntity(null, ContentType.TEXT_PLAIN)); } @Test void testWriteTo() throws Exception { final File tmpfile = File.createTempFile("testfile", ".txt"); tmpfile.deleteOnExit(); try (FileOutputStream outStream = new FileOutputStream(tmpfile)) { outStream.write(0); outStream.write(1); outStream.write(2); outStream.write(3); } try (final FileEntity httpentity = new FileEntity(tmpfile, ContentType.TEXT_PLAIN)) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); httpentity.writeTo(out); final byte[] bytes = out.toByteArray(); Assertions.assertNotNull(bytes); Assertions.assertEquals(tmpfile.length(), bytes.length); for (int i = 0; i < 4; i++) { Assertions.assertEquals(i, bytes[i]); } Assertions.assertTrue(tmpfile.delete(), "Failed to delete: " + tmpfile); Assertions.assertThrows(NullPointerException.class, () -> httpentity.writeTo(null)); } } } httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestHttpEntityWrapper.java0100644 0000000 0000000 00000007701 00000250600 027675 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ContentType; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link HttpEntityWrapper}. * */ class TestHttpEntityWrapper { @Test void testBasics() throws Exception { final StringEntity entity = new StringEntity("Message content", ContentType.TEXT_PLAIN, "blah", false); try (final HttpEntityWrapper wrapped = new HttpEntityWrapper(entity)) { Assertions.assertEquals(entity.getContentLength(), wrapped.getContentLength()); Assertions.assertEquals(entity.getContentType(), wrapped.getContentType()); Assertions.assertEquals(entity.getContentEncoding(), wrapped.getContentEncoding()); Assertions.assertEquals(entity.isChunked(), wrapped.isChunked()); Assertions.assertEquals(entity.isRepeatable(), wrapped.isRepeatable()); Assertions.assertEquals(entity.isStreaming(), wrapped.isStreaming()); Assertions.assertNotNull(wrapped.getContent()); } } @Test void testIllegalConstructor() { Assertions.assertThrows(NullPointerException.class, () -> new HttpEntityWrapper(null)); } @Test void testWriteTo() throws Exception { final String s = "Message content"; final byte[] bytes = s.getBytes(StandardCharsets.US_ASCII); final StringEntity entity = new StringEntity(s); try (final HttpEntityWrapper wrapped = new HttpEntityWrapper(entity)) { ByteArrayOutputStream out = new ByteArrayOutputStream(); wrapped.writeTo(out); byte[] bytes2 = out.toByteArray(); Assertions.assertNotNull(bytes2); Assertions.assertEquals(bytes.length, bytes2.length); for (int i = 0; i < bytes.length; i++) { Assertions.assertEquals(bytes[i], bytes2[i]); } out = new ByteArrayOutputStream(); wrapped.writeTo(out); bytes2 = out.toByteArray(); Assertions.assertNotNull(bytes2); Assertions.assertEquals(bytes.length, bytes2.length); for (int i = 0; i < bytes.length; i++) { Assertions.assertEquals(bytes[i], bytes2[i]); } Assertions.assertThrows(NullPointerException.class, () -> wrapped.writeTo(null)); } } @Test void testConsumeContent() throws Exception { final String s = "Message content"; final StringEntity entity = new StringEntity(s); final HttpEntityWrapper wrapped = new HttpEntityWrapper(entity); EntityUtils.consume(wrapped); Assertions.assertDoesNotThrow(() -> EntityUtils.consume(wrapped)); } } httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestInputStreamEntity.java0100644 0000000 0000000 00000012400 00000250600 027660 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ContentType; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link InputStreamEntity}. * */ class TestInputStreamEntity { @Test void testBasics() throws Exception { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); final InputStreamEntity entity = new InputStreamEntity(new ByteArrayInputStream(bytes), bytes.length, null); Assertions.assertEquals(bytes.length, entity.getContentLength()); Assertions.assertNotNull(entity.getContent()); Assertions.assertFalse(entity.isRepeatable()); Assertions.assertTrue(entity.isStreaming()); } @Test void testNullConstructor() { Assertions.assertThrows(NullPointerException.class, () -> new InputStreamEntity(null, 0, null)); } @Test void testUnknownLengthConstructor() throws Exception { try (final InputStreamEntity entity = new InputStreamEntity(EmptyInputStream.INSTANCE, null)) { Assertions.assertEquals(-1, entity.getContentLength()); } } @Test void testWriteTo() throws Exception { final String message = "Message content"; final byte[] bytes = message.getBytes(StandardCharsets.US_ASCII); final InputStream inStream = new ByteArrayInputStream(bytes); try (final InputStreamEntity entity = new InputStreamEntity(inStream, bytes.length, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.US_ASCII))) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); entity.writeTo(out); final byte[] writtenBytes = out.toByteArray(); Assertions.assertNotNull(writtenBytes); Assertions.assertEquals(bytes.length, writtenBytes.length); final String s = new String(writtenBytes, StandardCharsets.US_ASCII.name()); Assertions.assertEquals(message, s); } } @Test void testWriteToPartialContent() throws Exception { final String message = "Message content"; final byte[] bytes = message.getBytes(StandardCharsets.US_ASCII); final InputStream inStream = new ByteArrayInputStream(bytes); final int contentLength = 7; try (final InputStreamEntity entity = new InputStreamEntity(inStream, contentLength, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.US_ASCII))) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); entity.writeTo(out); final byte[] writtenBytes = out.toByteArray(); Assertions.assertNotNull(writtenBytes); Assertions.assertEquals(contentLength, writtenBytes.length); final String s = new String(writtenBytes, StandardCharsets.US_ASCII.name()); Assertions.assertEquals(message.substring(0, contentLength), s); } } @Test void testWriteToUnknownLength() throws Exception { final String message = "Message content"; final byte[] bytes = message.getBytes(StandardCharsets.US_ASCII); final InputStreamEntity entity = new InputStreamEntity(new ByteArrayInputStream(bytes), ContentType.TEXT_PLAIN.withCharset(StandardCharsets.US_ASCII)); final ByteArrayOutputStream out = new ByteArrayOutputStream(); entity.writeTo(out); final byte[] writtenBytes = out.toByteArray(); Assertions.assertNotNull(writtenBytes); Assertions.assertEquals(bytes.length, writtenBytes.length); final String s = new String(writtenBytes, StandardCharsets.US_ASCII.name()); Assertions.assertEquals(message, s); } @Test void testWriteToNull() throws Exception { try (final InputStreamEntity entity = new InputStreamEntity(EmptyInputStream.INSTANCE, 0, null)) { Assertions.assertThrows(NullPointerException.class, () -> entity.writeTo(null)); } } } httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestNullEntity.java0100644 0000000 0000000 00000005427 00000250600 026332 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Collections; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; class TestNullEntity { @Test void testLength() { assertEquals(0, NullEntity.INSTANCE.getContentLength()); } @Test void testContentType() { assertNull(NullEntity.INSTANCE.getContentType()); } @Test void testContentEncoding() { assertNull(NullEntity.INSTANCE.getContentEncoding()); } @Test void testTrailerNames() { assertEquals(Collections.emptySet(), NullEntity.INSTANCE.getTrailerNames()); } @Test void testContentStream() throws IOException { try (InputStream content = NullEntity.INSTANCE.getContent()) { assertEquals(-1, content.read()); } // Closing the resource should have no impact try (InputStream content = NullEntity.INSTANCE.getContent()) { assertEquals(-1, content.read()); } } @Test void testWriteTo() throws IOException { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); NullEntity.INSTANCE.writeTo(baos); assertEquals(0, baos.size()); } @Test void testIsStreaming() { assertFalse(NullEntity.INSTANCE.isStreaming()); } @Test void testIsChunked() { assertFalse(NullEntity.INSTANCE.isChunked()); } }httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestPathEntity.java0100644 0000000 0000000 00000007466 00000250600 026321 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import org.apache.hc.core5.http.ContentType; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link PathEntity}. */ class TestPathEntity { @Test void testBasics() throws Exception { final Path tmpPath = Files.createTempFile("testfile", ".txt"); // Mark the file for deletion on VM exit if an assertion fails. tmpPath.toFile().deleteOnExit(); try (final PathEntity httpEntity = new PathEntity(tmpPath, ContentType.TEXT_PLAIN)) { Assertions.assertEquals(Files.size(tmpPath), httpEntity.getContentLength()); try (final InputStream content = httpEntity.getContent()) { Assertions.assertNotNull(content); } Assertions.assertTrue(httpEntity.isRepeatable()); Assertions.assertFalse(httpEntity.isStreaming()); // If we can't delete the file now, then the PathEntity or test is hanging on to a file handle. Assertions.assertTrue(Files.deleteIfExists(tmpPath), "Failed to delete " + tmpPath); } } @Test void testNullConstructor() { Assertions.assertThrows(NullPointerException.class, () -> new PathEntity(null, ContentType.TEXT_PLAIN)); } @Test void testWriteTo() throws Exception { final Path tmpPath = Files.createTempFile("testfile", ".txt"); // Mark the file for deletion on VM exit if an assertion fails. tmpPath.toFile().deleteOnExit(); try (final OutputStream outStream = Files.newOutputStream(tmpPath)) { outStream.write(0); outStream.write(1); outStream.write(2); outStream.write(3); } try (final PathEntity httpEntity = new PathEntity(tmpPath, ContentType.TEXT_PLAIN)) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); httpEntity.writeTo(out); final byte[] bytes = out.toByteArray(); Assertions.assertNotNull(bytes); Assertions.assertEquals(Files.size(tmpPath), bytes.length); for (int i = 0; i < 4; i++) { Assertions.assertEquals(i, bytes[i]); } // If we can't delete the file now, then the PathEntity or test is hanging on to a file handle Assertions.assertTrue(Files.deleteIfExists(tmpPath), "Failed to delete " + tmpPath); Assertions.assertThrows(NullPointerException.class, () -> httpEntity.writeTo(null)); } } } httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestSerializableEntity.java0100644 0000000 0000000 00000006420 00000250600 030020 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestSerializableEntity { public static class SerializableObject implements Serializable { private static final long serialVersionUID = 1833335861188359573L; public final int intValue = 4; public final String stringValue = "Hello"; public SerializableObject() { } } @Test void testBasics() throws Exception { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ObjectOutputStream out = new ObjectOutputStream(baos); final Serializable serializableObj = new SerializableObject(); out.writeObject(serializableObj); try (final SerializableEntity httpentity = new SerializableEntity(serializableObj, null)) { Assertions.assertEquals(-1, httpentity.getContentLength()); Assertions.assertNotNull(httpentity.getContent()); Assertions.assertTrue(httpentity.isRepeatable()); Assertions.assertFalse(httpentity.isStreaming()); } } @Test void testWriteTo() throws Exception { final Serializable serializableObj = new SerializableObject(); try (final SerializableEntity httpentity = new SerializableEntity(serializableObj, null)) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); httpentity.writeTo(out); final byte[] bytes = out.toByteArray(); Assertions.assertNotNull(bytes); final ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(bytes)); final SerializableObject serIn = (SerializableObject) oin.readObject(); Assertions.assertEquals(4, serIn.intValue); Assertions.assertEquals("Hello", serIn.stringValue); Assertions.assertThrows(NullPointerException.class, () -> httpentity.writeTo(null)); } } } httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestStringEntity.java0100644 0000000 0000000 00000011774 00000250600 026670 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.entity; import java.io.ByteArrayOutputStream; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ContentType; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link StringEntity}. */ class TestStringEntity { @Test void testBasics() throws Exception { final String s = "Message content"; try (final StringEntity httpentity = new StringEntity(s, ContentType.TEXT_PLAIN)) { final byte[] bytes = s.getBytes(StandardCharsets.US_ASCII); Assertions.assertEquals(bytes.length, httpentity.getContentLength()); Assertions.assertNotNull(httpentity.getContent()); Assertions.assertTrue(httpentity.isRepeatable()); Assertions.assertFalse(httpentity.isStreaming()); } } @Test void testNullConstructor() { Assertions.assertThrows(NullPointerException.class, () -> new StringEntity(null)); } @Test void testDefaultContent() throws Exception { final String s = "Message content"; StringEntity httpentity = new StringEntity(s, ContentType.create("text/csv", "ANSI_X3.4-1968")); Assertions.assertEquals("text/csv; charset=US-ASCII", httpentity.getContentType()); httpentity = new StringEntity(s, StandardCharsets.US_ASCII); Assertions.assertEquals("text/plain; charset=US-ASCII", httpentity.getContentType()); httpentity = new StringEntity(s); Assertions.assertEquals("text/plain; charset=UTF-8", httpentity.getContentType()); } private static String constructString(final int [] unicodeChars) { final StringBuilder buffer = new StringBuilder(); if (unicodeChars != null) { for (final int unicodeChar : unicodeChars) { buffer.append((char)unicodeChar); } } return buffer.toString(); } static final int SWISS_GERMAN_HELLO [] = { 0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4 }; @Test void testNullCharset() throws Exception { final String s = constructString(SWISS_GERMAN_HELLO); StringEntity httpentity = new StringEntity(s, ContentType.create("text/plain", (Charset) null)); Assertions.assertNotNull(httpentity.getContentType()); Assertions.assertEquals("text/plain", httpentity.getContentType()); Assertions.assertEquals(s, EntityUtils.toString(httpentity)); httpentity = new StringEntity(s, (Charset) null); Assertions.assertNotNull(httpentity.getContentType()); Assertions.assertEquals("text/plain", httpentity.getContentType()); Assertions.assertEquals(s, EntityUtils.toString(httpentity)); } @Test void testWriteTo() throws Exception { final String s = "Message content"; final byte[] bytes = s.getBytes(StandardCharsets.UTF_8); try (final StringEntity httpentity = new StringEntity(s)) { ByteArrayOutputStream out = new ByteArrayOutputStream(); httpentity.writeTo(out); byte[] bytes2 = out.toByteArray(); Assertions.assertNotNull(bytes2); Assertions.assertEquals(bytes.length, bytes2.length); for (int i = 0; i < bytes.length; i++) { Assertions.assertEquals(bytes[i], bytes2[i]); } out = new ByteArrayOutputStream(); httpentity.writeTo(out); bytes2 = out.toByteArray(); Assertions.assertNotNull(bytes2); Assertions.assertEquals(bytes.length, bytes2.length); for (int i = 0; i < bytes.length; i++) { Assertions.assertEquals(bytes[i], bytes2[i]); } Assertions.assertThrows(NullPointerException.class, () -> httpentity.writeTo(null)); } } } httpcore5/src/test/java/org/apache/hc/core5/http/io/support/0040755 0000000 0000000 00000000000 00000250600 022713 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicRequestBuilderTest.java0100644 0000000 0000000 00000031406 00000250600 030660 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.support; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import java.net.URISyntaxException; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.io.entity.ByteArrayEntity; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BasicNameValuePair; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.net.URIBuilder; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class ClassicRequestBuilderTest { @Test void constructor() throws UnknownHostException, URISyntaxException { final ClassicRequestBuilder classicRequestBuilder = new ClassicRequestBuilder(Method.HEAD); assertEquals(Method.HEAD.name(), classicRequestBuilder.getMethod()); final ClassicRequestBuilder classicRequestBuilder2 = new ClassicRequestBuilder(Method.HEAD.name()); assertEquals(Method.HEAD.name(), classicRequestBuilder2.getMethod()); final ClassicRequestBuilder classicRequestBuilder3 = new ClassicRequestBuilder(Method.HEAD.name(), URIBuilder.localhost().build()); assertEquals(Method.HEAD.name(), classicRequestBuilder3.getMethod()); assertEquals(URIBuilder.localhost().getHost(), classicRequestBuilder3.getAuthority().getHostName()); final ClassicRequestBuilder classicRequestBuilder4 = new ClassicRequestBuilder(Method.HEAD, URIBuilder.localhost().build()); assertEquals(Method.HEAD.name(), classicRequestBuilder4.getMethod()); assertEquals(URIBuilder.localhost().getHost(), classicRequestBuilder4.getAuthority().getHostName()); final ClassicRequestBuilder classicRequestBuilder5 = new ClassicRequestBuilder(Method.HEAD, "/localhost"); assertEquals(Method.HEAD.name(), classicRequestBuilder5.getMethod()); assertEquals("/localhost", classicRequestBuilder5.getPath()); final ClassicRequestBuilder classicRequestBuilder6 = new ClassicRequestBuilder(Method.HEAD.name(), "/localhost"); assertEquals(Method.HEAD.name(), classicRequestBuilder6.getMethod()); assertEquals("/localhost", classicRequestBuilder6.getPath()); } @Test void create() { final ClassicHttpRequest classicHttpRequest = ClassicRequestBuilder.create(Method.HEAD.name()).build(); assertEquals(Method.HEAD.name(), classicHttpRequest.getMethod()); } @Test void get() throws UnknownHostException, URISyntaxException { final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.get(); assertEquals(Method.GET.name(), classicRequestBuilder.getMethod()); final ClassicRequestBuilder classicRequestBuilder1 = ClassicRequestBuilder.get(URIBuilder.localhost().build()); assertEquals(Method.GET.name(), classicRequestBuilder1.getMethod()); final ClassicRequestBuilder classicRequestBuilder3 = ClassicRequestBuilder.get("/localhost"); assertEquals(Method.GET.name(), classicRequestBuilder3.getMethod()); assertEquals("/localhost", classicRequestBuilder3.getPath()); } @Test void head() throws UnknownHostException, URISyntaxException { final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.head(); assertEquals(Method.HEAD.name(), classicRequestBuilder.getMethod()); final ClassicRequestBuilder classicRequestBuilder1 = ClassicRequestBuilder.head(URIBuilder.localhost().build()); assertEquals(Method.HEAD.name(), classicRequestBuilder1.getMethod()); final ClassicRequestBuilder classicRequestBuilder3 = ClassicRequestBuilder.head("/localhost"); assertEquals(Method.HEAD.name(), classicRequestBuilder3.getMethod()); assertEquals("/localhost", classicRequestBuilder3.getPath()); } @Test void query() throws UnknownHostException, URISyntaxException { final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.query(); assertEquals(Method.QUERY.name(), classicRequestBuilder.getMethod()); final ClassicRequestBuilder classicRequestBuilder1 = ClassicRequestBuilder.query(URIBuilder.localhost().build()); assertEquals(Method.QUERY.name(), classicRequestBuilder1.getMethod()); final ClassicRequestBuilder classicRequestBuilder3 = ClassicRequestBuilder.query("/localhost"); assertEquals(Method.QUERY.name(), classicRequestBuilder3.getMethod()); assertEquals("/localhost", classicRequestBuilder3.getPath()); } @Test void patch() throws UnknownHostException, URISyntaxException { final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.patch(); assertEquals(Method.PATCH.name(), classicRequestBuilder.getMethod()); final ClassicRequestBuilder classicRequestBuilder1 = ClassicRequestBuilder.patch(URIBuilder.localhost().build()); assertEquals(Method.PATCH.name(), classicRequestBuilder1.getMethod()); final ClassicRequestBuilder classicRequestBuilder3 = ClassicRequestBuilder.patch("/localhost"); assertEquals(Method.PATCH.name(), classicRequestBuilder3.getMethod()); assertEquals("/localhost", classicRequestBuilder3.getPath()); } @Test void post() throws UnknownHostException, URISyntaxException { final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.post(); assertEquals(Method.POST.name(), classicRequestBuilder.getMethod()); final ClassicRequestBuilder classicRequestBuilder1 = ClassicRequestBuilder.post(URIBuilder.localhost().build()); assertEquals(Method.POST.name(), classicRequestBuilder1.getMethod()); final ClassicRequestBuilder classicRequestBuilder3 = ClassicRequestBuilder.post("/localhost"); assertEquals(Method.POST.name(), classicRequestBuilder3.getMethod()); assertEquals("/localhost", classicRequestBuilder3.getPath()); } @Test void put() throws UnknownHostException, URISyntaxException { final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.put(); assertEquals(Method.PUT.name(), classicRequestBuilder.getMethod()); final ClassicRequestBuilder classicRequestBuilder1 = ClassicRequestBuilder.put(URIBuilder.localhost().build()); assertEquals(Method.PUT.name(), classicRequestBuilder1.getMethod()); final ClassicRequestBuilder classicRequestBuilder3 = ClassicRequestBuilder.put("/localhost"); assertEquals(Method.PUT.name(), classicRequestBuilder3.getMethod()); assertEquals("/localhost", classicRequestBuilder3.getPath()); } @Test void delete() throws UnknownHostException, URISyntaxException { final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.delete(); assertEquals(Method.DELETE.name(), classicRequestBuilder.getMethod()); final ClassicRequestBuilder classicRequestBuilder1 = ClassicRequestBuilder.delete(URIBuilder.localhost().build()); assertEquals(Method.DELETE.name(), classicRequestBuilder1.getMethod()); final ClassicRequestBuilder classicRequestBuilder3 = ClassicRequestBuilder.delete("/localhost"); assertEquals(Method.DELETE.name(), classicRequestBuilder3.getMethod()); assertEquals("/localhost", classicRequestBuilder3.getPath()); } @Test void trace() throws UnknownHostException, URISyntaxException { final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.trace(); assertEquals(Method.TRACE.name(), classicRequestBuilder.getMethod()); final ClassicRequestBuilder classicRequestBuilder1 = ClassicRequestBuilder.trace(URIBuilder.localhost().build()); assertEquals(Method.TRACE.name(), classicRequestBuilder1.getMethod()); final ClassicRequestBuilder classicRequestBuilder3 = ClassicRequestBuilder.trace("/localhost"); assertEquals(Method.TRACE.name(), classicRequestBuilder3.getMethod()); assertEquals("/localhost", classicRequestBuilder3.getPath()); } @Test void option() throws UnknownHostException, URISyntaxException { final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.options(); assertEquals(Method.OPTIONS.name(), classicRequestBuilder.getMethod()); final ClassicRequestBuilder classicRequestBuilder1 = ClassicRequestBuilder.options(URIBuilder.localhost().build()); assertEquals(Method.OPTIONS.name(), classicRequestBuilder1.getMethod()); final ClassicRequestBuilder classicRequestBuilder3 = ClassicRequestBuilder.options("/localhost"); assertEquals(Method.OPTIONS.name(), classicRequestBuilder3.getMethod()); assertEquals("/localhost", classicRequestBuilder3.getPath()); } @Test void builder() { final Header header = new BasicHeader("header2", "blah"); final ClassicHttpRequest classicHttpRequest = ClassicRequestBuilder.get() .setVersion(HttpVersion.HTTP_1_1) .setCharset(StandardCharsets.US_ASCII) .setAuthority(new URIAuthority("host")) .setEntity("

Access denied

", ContentType.TEXT_HTML) .setHeader(new BasicHeader("header2", "blah")) .setHeader("X-Test-Filter", "active") .setHeader(header) .setPath("path/") .setScheme("http") .addHeader(header) .addHeader("header", ".addHeader(header)") .addParameter(new BasicHeader("header2", "blah")) .addParameter("param1", "value1") .addParameters(new BasicNameValuePair("param3", "value3"), new BasicNameValuePair("param4", null)) .setAbsoluteRequestUri(true) .setEntity(new StringEntity("requestBody")) .setEntity(new ByteArrayEntity(new byte[10240], ContentType.TEXT_PLAIN)) .setEntity(new byte[10240], ContentType.TEXT_HTML) .setEntity("requestBody") .setUri("theUri") .setHttpHost(new HttpHost("httpbin.org")) .build(); assertAll("Should return address of Oracle's headquarter", () -> assertNotNull(classicHttpRequest.getEntity()), () -> assertEquals(Method.GET.name(), classicHttpRequest.getMethod()), () -> assertEquals("http", classicHttpRequest.getScheme()), () -> assertEquals("httpbin.org", classicHttpRequest.getAuthority().getHostName()), () -> assertEquals(HttpVersion.HTTP_1_1, classicHttpRequest.getVersion()), () -> assertEquals(4, classicHttpRequest.getHeaders().length), () -> assertNotNull(ClassicRequestBuilder.get().toString()), () -> assertEquals("http://httpbin.org/theUri?header2=blah¶m1=value1¶m3=value3¶m4", new String(classicHttpRequest.getRequestUri().getBytes())) ); } @Test void builderTraceThrowsIllegalStateException() { Assertions.assertThrows(IllegalStateException.class, () -> ClassicRequestBuilder.trace() .setVersion(HttpVersion.HTTP_1_1) .setEntity(new ByteArrayEntity(new byte[10240], ContentType.TEXT_PLAIN)) .build()); } }httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicResponseBuilderTest.java0100644 0000000 0000000 00000015040 00000250600 031022 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.io.support; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.Socket; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy; import org.apache.hc.core5.http.impl.io.DefaultBHttpServerConnection; import org.apache.hc.core5.http.impl.io.DefaultHttpRequestParserFactory; import org.apache.hc.core5.http.impl.io.DefaultHttpResponseWriterFactory; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.BasicHeader; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; class ClassicResponseBuilderTest { @Mock Socket socket; DefaultBHttpServerConnection conn; ByteArrayOutputStream outStream; @BeforeEach void prepareMocks() throws IOException { MockitoAnnotations.openMocks(this); conn = new DefaultBHttpServerConnection("http", Http1Config.DEFAULT, null, null, DefaultContentLengthStrategy.INSTANCE, DefaultContentLengthStrategy.INSTANCE, DefaultHttpRequestParserFactory.INSTANCE, DefaultHttpResponseWriterFactory.INSTANCE); outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); conn.bind(socket); Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount()); } @Test void create() throws IOException, HttpException { final ClassicHttpResponse response = ClassicResponseBuilder.create(200) .setHeader("X-Test-Filter", "active") .addHeader("header1", "blah") .setHeader(new BasicHeader("header2", "blah")) .addHeader(new BasicHeader("header3", "blah")) .setVersion(HttpVersion.HTTP_1_1) .setEntity("

Access OK

", ContentType.TEXT_HTML) .setEntity("Another entity") .build(); response.addHeader("User-Agent", "test"); conn.sendResponseHeader(response); conn.flush(); Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount()); final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("HTTP/1.1 200 OK\r\nX-Test-Filter: active\r\nheader1: blah\r\nheader2: blah\r\nheader3: blah\r\nUser-Agent: test\r\n\r\n", s); Assertions.assertNotNull(response.getEntity()); } @Test void remove() throws IOException, HttpException { final Header header = new BasicHeader("header2", "blah"); final ClassicHttpResponse response = ClassicResponseBuilder.create(200) .setEntity(new StringEntity("123", ContentType.TEXT_PLAIN)) .setHeader("X-Test-Filter", "active") .addHeader("header1", "blah") .setHeader(header) .addHeader(new BasicHeader("header3", "blah")) .setVersion(HttpVersion.HTTP_1_1) .setEntity("

Access OK

", ContentType.TEXT_HTML) .setEntity("Another entity") .removeHeader(header) .build(); response.removeHeaders("X-Test-Filter"); conn.sendResponseHeader(response); conn.flush(); Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount()); final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("HTTP/1.1 200 OK\r\nheader1: blah\r\nheader3: blah\r\n\r\n", s); Assertions.assertNotNull(response.getEntity()); } @Test void copy() throws IOException, HttpException { final Header header = new BasicHeader("header2", "blah"); final ClassicHttpResponse response = ClassicResponseBuilder.create(200) .setEntity(new StringEntity("123", ContentType.TEXT_PLAIN)) .addHeader("header1", "blah") .setHeader(header) .addHeader(new BasicHeader("header3", "blah")) .setVersion(HttpVersion.HTTP_1_1) .setEntity("

Access OK

", ContentType.TEXT_HTML) .setEntity("Another entity") .removeHeader(header) .build(); final ClassicResponseBuilder classicResponseBuilder = ClassicResponseBuilder.copy(response); final ClassicHttpResponse response2 = classicResponseBuilder.build(); conn.sendResponseHeader(response2); conn.flush(); Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount()); final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("HTTP/1.1 200 OK\r\nheader1: blah\r\nheader3: blah\r\n\r\n", s); Assertions.assertNotNull(response.getEntity()); Assertions.assertNotNull(classicResponseBuilder.toString()); } }httpcore5/src/test/java/org/apache/hc/core5/http/message/0040755 0000000 0000000 00000000000 00000250600 022214 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/message/HttpRequestWrapperTest.java0100644 0000000 0000000 00000021156 00000250600 027552 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.net.URI; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.net.URIAuthority; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class HttpRequestWrapperTest { @Test void testRequestBasics() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, "/stuff"); final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request); Assertions.assertEquals(Method.GET.name(), httpRequestWrapper.getMethod()); Assertions.assertEquals("/stuff", httpRequestWrapper.getPath()); Assertions.assertNull(httpRequestWrapper.getAuthority()); Assertions.assertEquals(new URI("/stuff"), httpRequestWrapper.getUri()); httpRequestWrapper.setPath("/another-stuff"); Assertions.assertEquals("/another-stuff", httpRequestWrapper.getPath()); } @Test void testDefaultRequestConstructors() { final HttpRequest request1 = new BasicHttpRequest("WHATEVER", "/"); final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request1); Assertions.assertEquals("WHATEVER", httpRequestWrapper.getMethod()); Assertions.assertEquals("/", httpRequestWrapper.getPath()); final HttpRequest request2 = new BasicHttpRequest(Method.GET, "/"); final HttpRequestWrapper httpRequestWrapper2 = new HttpRequestWrapper(request2); Assertions.assertEquals(Method.GET.name(), httpRequestWrapper2.getMethod()); Assertions.assertEquals("/", httpRequestWrapper2.getPath()); Assertions.assertThrows(NullPointerException.class, () -> new BasicHttpRequest(Method.GET, (URI) null)); } @Test void testRequestWithRelativeURI() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, new URI("/stuff")); final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request); Assertions.assertEquals(Method.GET.name(), httpRequestWrapper.getMethod()); Assertions.assertEquals("/stuff", httpRequestWrapper.getPath()); Assertions.assertNull(httpRequestWrapper.getAuthority()); Assertions.assertEquals(new URI("/stuff"), httpRequestWrapper.getUri()); } @Test void testRequestWithAbsoluteURI() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, new URI("https://host:9443/stuff?param=value")); final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request); Assertions.assertEquals(Method.GET.name(), httpRequestWrapper.getMethod()); Assertions.assertEquals("/stuff?param=value", httpRequestWrapper.getPath()); Assertions.assertEquals(new URIAuthority("host", 9443), httpRequestWrapper.getAuthority()); Assertions.assertEquals("https", httpRequestWrapper.getScheme()); Assertions.assertEquals(new URI("https://host:9443/stuff?param=value"), httpRequestWrapper.getUri()); httpRequestWrapper.setScheme(URIScheme.HTTP.id); Assertions.assertEquals("http", httpRequestWrapper.getScheme()); } @Test void testRequestWithAbsoluteURIAsPath() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, "https://host:9443/stuff?param=value"); final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request); Assertions.assertEquals(Method.GET.name(), httpRequestWrapper.getMethod()); Assertions.assertEquals("/stuff?param=value", httpRequestWrapper.getPath()); Assertions.assertEquals(new URIAuthority("host", 9443), httpRequestWrapper.getAuthority()); Assertions.assertEquals("https", httpRequestWrapper.getScheme()); Assertions.assertEquals(new URI("https://host:9443/stuff?param=value"), httpRequestWrapper.getUri()); } @Test void testRequestWithNoPath() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, new URI("http://host")); final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request); Assertions.assertEquals(Method.GET.name(), httpRequestWrapper.getMethod()); Assertions.assertEquals("/", httpRequestWrapper.getPath()); Assertions.assertEquals(new URIAuthority("host"), httpRequestWrapper.getAuthority()); Assertions.assertEquals("http", httpRequestWrapper.getScheme()); Assertions.assertEquals(new URI("http://host/"), httpRequestWrapper.getUri()); } @Test void testRequestWithUserInfo() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, new URI("https://user:pwd@host:9443/stuff?param=value")); final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request); Assertions.assertEquals(Method.GET.name(), httpRequestWrapper.getMethod()); Assertions.assertEquals("/stuff?param=value", httpRequestWrapper.getPath()); Assertions.assertEquals(new URIAuthority("user:pwd", "host", 9443), httpRequestWrapper.getAuthority()); Assertions.assertEquals("https", httpRequestWrapper.getScheme()); Assertions.assertEquals(new URI("https://host:9443/stuff?param=value"), httpRequestWrapper.getUri()); } @Test void testRequestWithAuthority() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, new HttpHost("http", "somehost", -1), "/stuff"); final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request); Assertions.assertEquals(Method.GET.name(), httpRequestWrapper.getMethod()); Assertions.assertEquals("/stuff", httpRequestWrapper.getPath()); Assertions.assertEquals(new URIAuthority("somehost"), httpRequestWrapper.getAuthority()); Assertions.assertEquals(new URI("http://somehost/stuff"), httpRequestWrapper.getUri()); httpRequestWrapper.setAuthority(new URIAuthority("newHost")); Assertions.assertEquals(new URIAuthority("newHost"), httpRequestWrapper.getAuthority()); } @Test void testRequestWithAuthorityRelativePath() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, new HttpHost("http", "somehost", -1), "stuff"); final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request); Assertions.assertEquals(Method.GET.name(), httpRequestWrapper.getMethod()); Assertions.assertEquals("stuff", httpRequestWrapper.getPath()); Assertions.assertEquals("stuff", httpRequestWrapper.getRequestUri()); Assertions.assertEquals(new URIAuthority("somehost"), httpRequestWrapper.getAuthority()); Assertions.assertEquals(new URI("http://somehost/stuff"), httpRequestWrapper.getUri()); } @Test void testRequestHostWithReservedChars() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, URI.create("http://someuser%21@%21example%21.com/stuff")); final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request); Assertions.assertEquals(Method.GET.name(), httpRequestWrapper.getMethod()); Assertions.assertEquals("/stuff", httpRequestWrapper.getPath()); Assertions.assertEquals(new URIAuthority("someuser%21", "%21example%21.com", -1), httpRequestWrapper.getAuthority()); Assertions.assertEquals(new URI("http://%21example%21.com/stuff"), httpRequestWrapper.getUri()); } }httpcore5/src/test/java/org/apache/hc/core5/http/message/HttpResponseWrapperTest.java0100644 0000000 0000000 00000007563 00000250600 027726 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.util.Locale; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class HttpResponseWrapperTest { @Test void testDefaultResponseConstructors() { final HttpResponse response1 = new BasicHttpResponse(HttpStatus.SC_BAD_REQUEST, "Bad Request"); final HttpResponseWrapper httpResponseWrapper1 = new HttpResponseWrapper(response1); Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, httpResponseWrapper1.getCode()); final HttpResponse response2 = new BasicHttpResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR, "whatever"); final HttpResponseWrapper httpResponseWrapper2 = new HttpResponseWrapper(response2); Assertions.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, httpResponseWrapper2.getCode()); Assertions.assertEquals("whatever", httpResponseWrapper2.getReasonPhrase()); httpResponseWrapper2.setReasonPhrase("another-whatever"); Assertions.assertEquals("another-whatever", httpResponseWrapper2.getReasonPhrase()); } @Test void testSetResponseStatus() { final HttpResponse response1 = new BasicHttpResponse(200, "OK"); final HttpResponseWrapper httpResponseWrapper1 = new HttpResponseWrapper(response1); Assertions.assertEquals(200, httpResponseWrapper1.getCode()); final HttpResponse response2 = new BasicHttpResponse(HttpStatus.SC_BAD_REQUEST, "Bad Request"); final HttpResponseWrapper httpResponseWrapper2 = new HttpResponseWrapper(response2); Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, httpResponseWrapper2.getCode()); final HttpResponse response3 = new BasicHttpResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR, "whatever"); final HttpResponseWrapper httpResponseWrapper3 = new HttpResponseWrapper(response3); Assertions.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, httpResponseWrapper3.getCode()); Assertions.assertEquals("whatever", httpResponseWrapper3.getReasonPhrase()); final HttpResponse response4 = new BasicHttpResponse(HttpStatus.SC_OK, "OK"); final HttpResponseWrapper httpResponseWrapper4 = new HttpResponseWrapper(response4); Assertions.assertThrows(IllegalArgumentException.class, () -> httpResponseWrapper4.setCode(-23)); } @Test void testLocale() { final HttpResponse response = new BasicHttpResponse(200, "OK"); final HttpResponseWrapper httpResponseWrapper = new HttpResponseWrapper(response); httpResponseWrapper.setLocale(Locale.US); Assertions.assertEquals("US", httpResponseWrapper.getLocale().getCountry()); } }httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeader.java0100644 0000000 0000000 00000004045 00000250600 026051 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.http.Header; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Tests for {@link BasicHeader}. */ class TestBasicHeader { @Test void testConstructor() { final Header param = new BasicHeader("name", "value"); Assertions.assertEquals("name", param.getName()); Assertions.assertEquals("value", param.getValue()); } @Test void testInvalidName() { Assertions.assertThrows(NullPointerException.class, () -> new BasicHeader(null, null)); } @Test void testToString() { final Header param1 = new BasicHeader("name1", "value1"); Assertions.assertEquals("name1: value1", param1.toString()); final Header param2 = new BasicHeader("name1", null); Assertions.assertEquals("name1: ", param2.toString()); } } httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderElementIterator.java0100644 0000000 0000000 00000010212 00000250600 031066 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.util.Iterator; import java.util.NoSuchElementException; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HeaderElement; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Tests for {@link BasicHeaderElementIterator}. * */ class TestBasicHeaderElementIterator { @Test void testMultiHeader() { final Header[] headers = new Header[]{ new BasicHeader("Name", "value0"), new BasicHeader("Name", "value1") }; final Iterator hei = new BasicHeaderElementIterator( new BasicHeaderIterator(headers, "Name")); Assertions.assertTrue(hei.hasNext()); HeaderElement elem = hei.next(); Assertions.assertEquals("value0", elem.getName(), "The two header values must be equal"); Assertions.assertTrue(hei.hasNext()); elem = hei.next(); Assertions.assertEquals("value1", elem.getName(), "The two header values must be equal"); Assertions.assertFalse(hei.hasNext()); Assertions.assertThrows(NoSuchElementException.class, () -> hei.next()); Assertions.assertFalse(hei.hasNext()); Assertions.assertThrows(NoSuchElementException.class, () -> hei.next()); } @Test void testMultiHeaderSameLine() { final Header[] headers = new Header[]{ new BasicHeader("name", "value0,value1"), new BasicHeader("nAme", "cookie1=1,cookie2=2") }; final Iterator hei = new BasicHeaderElementIterator( new BasicHeaderIterator(headers, "Name")); HeaderElement elem = hei.next(); Assertions.assertEquals("value0", elem.getName(), "The two header values must be equal"); elem = hei.next(); Assertions.assertEquals("value1", elem.getName(), "The two header values must be equal"); elem = hei.next(); Assertions.assertEquals("cookie1", elem.getName(), "The two header values must be equal"); Assertions.assertEquals("1", elem.getValue(), "The two header values must be equal"); elem = hei.next(); Assertions.assertEquals("cookie2", elem.getName(), "The two header values must be equal"); Assertions.assertEquals("2", elem.getValue(), "The two header values must be equal"); } @Test void testFringeCases() { final Header[] headers = new Header[]{ new BasicHeader("Name", null), new BasicHeader("Name", " "), new BasicHeader("Name", ",,,") }; final Iterator hei = new BasicHeaderElementIterator( new BasicHeaderIterator(headers, "Name")); Assertions.assertFalse(hei.hasNext()); Assertions.assertThrows(NoSuchElementException.class, () -> hei.next()); Assertions.assertFalse(hei.hasNext()); Assertions.assertThrows(NoSuchElementException.class, () -> hei.next()); } } httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderIterator.java0100644 0000000 0000000 00000025126 00000250600 027566 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.util.Iterator; import java.util.NoSuchElementException; import org.apache.hc.core5.http.Header; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Tests for {@link Iterator} of {@link org.apache.hc.core5.http.Header}s. * */ class TestBasicHeaderIterator { @Test void testAllSame() { final Header[] headers = new Header[]{ new BasicHeader("Name", "value0"), new BasicHeader("nAme", "value1, value1.1"), new BasicHeader("naMe", "value2=whatever"), new BasicHeader("namE", "value3;tag=nil"), }; // without filter Iterator
hit = new BasicHeaderIterator(headers, null); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[0], hit.next(), "0"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[1], hit.next(), "1"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[2], hit.next(), "2"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[3], hit.next(), "3"); Assertions.assertFalse(hit.hasNext()); // with filter hit = new BasicHeaderIterator(headers, "name"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[0], hit.next(), "0"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[1], hit.next(), "1"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[2], hit.next(), "2"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[3], hit.next(), "3"); Assertions.assertFalse(hit.hasNext()); } @Test void testFirstLastOneNone() { final Header[] headers = new Header[]{ new BasicHeader("match", "value0"), new BasicHeader("mismatch", "value1, value1.1"), new BasicHeader("single", "value2=whatever"), new BasicHeader("match", "value3;tag=nil"), }; // without filter Iterator
hit = new BasicHeaderIterator(headers, null); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[0], hit.next(), "0"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[1], hit.next(), "1"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[2], hit.next(), "2"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[3], hit.next(), "3"); Assertions.assertFalse(hit.hasNext()); // with filter, first & last hit = new BasicHeaderIterator(headers, "match"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[0], hit.next()); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[3], hit.next()); Assertions.assertFalse(hit.hasNext()); // with filter, one match hit = new BasicHeaderIterator(headers, "single"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[2], hit.next()); Assertions.assertFalse(hit.hasNext()); // with filter, no match hit = new BasicHeaderIterator(headers, "way-off"); Assertions.assertFalse(hit.hasNext()); } @Test void testInterspersed() { final Header[] headers = new Header[]{ new BasicHeader("yellow", "00"), new BasicHeader("maroon", "01"), new BasicHeader("orange", "02"), new BasicHeader("orange", "03"), new BasicHeader("orange", "04"), new BasicHeader("yellow", "05"), new BasicHeader("maroon", "06"), new BasicHeader("maroon", "07"), new BasicHeader("maroon", "08"), new BasicHeader("yellow", "09"), new BasicHeader("maroon", "0a"), new BasicHeader("yellow", "0b"), new BasicHeader("orange", "0c"), new BasicHeader("yellow", "0d"), new BasicHeader("orange", "0e"), }; // without filter Iterator
hit = new BasicHeaderIterator(headers, null); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[0], hit.next(), "0"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[1], hit.next(), "1"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[2], hit.next(), "2"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[3], hit.next(), "3"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[4], hit.next(), "4"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[5], hit.next(), "5"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[6], hit.next(), "6"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[7], hit.next(), "7"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[8], hit.next(), "8"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[9], hit.next(), "9"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[10], hit.next(), "a"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[11], hit.next(), "b"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[12], hit.next(), "c"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[13], hit.next(), "d"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[14], hit.next(), "e"); Assertions.assertFalse(hit.hasNext()); // yellow 0, 5, 9, 11, 13 hit = new BasicHeaderIterator(headers, "Yellow"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[0], hit.next()); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[5], hit.next(), "5"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[9], hit.next(), "9"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[11], hit.next(), "b"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[13], hit.next(), "d"); Assertions.assertFalse(hit.hasNext()); // maroon 1, 6, 7, 8, 10 hit = new BasicHeaderIterator(headers, "marOOn"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[1], hit.next(),"1"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[6], hit.next(),"6"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[7], hit.next(), "7"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[8], hit.next(), "8"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[10], hit.next(), "a"); Assertions.assertFalse(hit.hasNext()); // orange 2, 3, 4, 12, 14 hit = new BasicHeaderIterator(headers, "OranGe"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[2], hit.next()); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[3], hit.next()); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[4], hit.next(), "4"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[12], hit.next(), "b"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[14], hit.next(), "e"); Assertions.assertFalse(hit.hasNext()); } @Test void testInvalid() { Assertions.assertThrows(NullPointerException.class, () -> new BasicHeaderIterator(null, "whatever")); // this is not invalid final Iterator
hit = new BasicHeaderIterator(new Header[0], "whatever"); Assertions.assertFalse(hit.hasNext()); // but this is Assertions.assertThrows(NoSuchElementException.class, () -> hit.next()); } @Test void testRemaining() { // to satisfy Clover and take coverage to 100% final Header[] headers = new Header[]{ new BasicHeader("Name", "value0"), new BasicHeader("nAme", "value1, value1.1"), new BasicHeader("naMe", "value2=whatever"), new BasicHeader("namE", "value3;tag=nil"), }; // without filter, using plain next() final Iterator
hit = new BasicHeaderIterator(headers, null); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[0], hit.next(), "0"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[1], hit.next(), "1"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[2], hit.next(), "2"); Assertions.assertTrue(hit.hasNext()); Assertions.assertEquals(headers[3], hit.next(), "3"); Assertions.assertFalse(hit.hasNext()); final Iterator
hit2 = new BasicHeaderIterator(headers, null); Assertions.assertTrue(hit2.hasNext()); Assertions.assertThrows(UnsupportedOperationException.class, () -> hit2.remove()); } } httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderValueFormatter.java0100644 0000000 0000000 00000022210 00000250600 030724 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.http.HeaderElement; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.util.CharArrayBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** * Tests for header value formatting. * * */ class TestBasicHeaderValueFormatter { private BasicHeaderValueFormatter formatter; @BeforeEach void setup() { this.formatter = BasicHeaderValueFormatter.INSTANCE; } @Test void testNVPFormatting() { final NameValuePair param1 = new BasicNameValuePair("param", "regular_stuff"); final NameValuePair param2 = new BasicNameValuePair("param", "this\\that"); final NameValuePair param3 = new BasicNameValuePair("param", "this,that"); final NameValuePair param4 = new BasicNameValuePair("param", "quote marks (\") must be escaped"); final NameValuePair param5 = new BasicNameValuePair("param", "back slash (\\) must be escaped too"); final NameValuePair param6 = new BasicNameValuePair("param", "values with\tblanks must always be quoted"); final NameValuePair param7 = new BasicNameValuePair("param", null); final CharArrayBuffer buf = new CharArrayBuffer(64); buf.clear(); this.formatter.formatNameValuePair(buf, param1, false); Assertions.assertEquals("param=regular_stuff", buf.toString()); buf.clear(); this.formatter.formatNameValuePair(buf, param2, false); Assertions.assertEquals("param=\"this\\\\that\"", buf.toString()); buf.clear(); this.formatter.formatNameValuePair(buf, param3, false); Assertions.assertEquals("param=\"this,that\"", buf.toString()); buf.clear(); this.formatter.formatNameValuePair(buf, param4, false); Assertions.assertEquals("param=\"quote marks (\\\") must be escaped\"", buf.toString()); buf.clear(); this.formatter.formatNameValuePair(buf, param5, false); Assertions.assertEquals("param=\"back slash (\\\\) must be escaped too\"", buf.toString()); buf.clear(); this.formatter.formatNameValuePair(buf, param6, false); Assertions.assertEquals("param=\"values with\tblanks must always be quoted\"", buf.toString()); buf.clear(); this.formatter.formatNameValuePair(buf, param7, false); Assertions.assertEquals("param", buf.toString()); buf.clear(); this.formatter.formatNameValuePair(buf, param1, true); Assertions.assertEquals("param=\"regular_stuff\"", buf.toString()); buf.clear(); this.formatter.formatNameValuePair(buf, param2, true); Assertions.assertEquals("param=\"this\\\\that\"", buf.toString()); buf.clear(); this.formatter.formatNameValuePair(buf, param3, true); Assertions.assertEquals("param=\"this,that\"", buf.toString()); buf.clear(); this.formatter.formatNameValuePair(buf, param4, true); Assertions.assertEquals("param=\"quote marks (\\\") must be escaped\"", buf.toString()); buf.clear(); this.formatter.formatNameValuePair(buf, param5, true); Assertions.assertEquals("param=\"back slash (\\\\) must be escaped too\"", buf.toString()); buf.clear(); this.formatter.formatNameValuePair(buf, param6, true); Assertions.assertEquals("param=\"values with\tblanks must always be quoted\"", buf.toString()); buf.clear(); this.formatter.formatNameValuePair(buf, param7, true); Assertions.assertEquals("param", buf.toString()); } @Test void testParamsFormatting() { final NameValuePair param1 = new BasicNameValuePair("param", "regular_stuff"); final NameValuePair param2 = new BasicNameValuePair("param", "this\\that"); final NameValuePair param3 = new BasicNameValuePair("param", "this,that"); final NameValuePair[] params = new NameValuePair[] {param1, param2, param3}; final CharArrayBuffer buf = new CharArrayBuffer(64); buf.clear(); this.formatter.formatParameters(buf, params, false); Assertions.assertEquals("param=regular_stuff; param=\"this\\\\that\"; param=\"this,that\"", buf.toString()); buf.clear(); this.formatter.formatParameters(buf, params, true); Assertions.assertEquals("param=\"regular_stuff\"; param=\"this\\\\that\"; param=\"this,that\"", buf.toString()); } @Test void testHEFormatting() { final NameValuePair param1 = new BasicNameValuePair("param", "regular_stuff"); final NameValuePair param2 = new BasicNameValuePair("param", "this\\that"); final NameValuePair param3 = new BasicNameValuePair("param", "this,that"); final NameValuePair param4 = new BasicNameValuePair("param", null); final NameValuePair[] params = new NameValuePair[] {param1, param2, param3, param4}; final HeaderElement element = new BasicHeaderElement("name", "value", params); final CharArrayBuffer buf = new CharArrayBuffer(64); this.formatter.formatHeaderElement(buf, element, false); Assertions.assertEquals("name=value; param=regular_stuff; param=\"this\\\\that\"; param=\"this,that\"; param", buf.toString()); } @Test void testElementsFormatting() { final NameValuePair param1 = new BasicNameValuePair("param", "regular_stuff"); final NameValuePair param2 = new BasicNameValuePair("param", "this\\that"); final NameValuePair param3 = new BasicNameValuePair("param", "this,that"); final NameValuePair param4 = new BasicNameValuePair("param", null); final HeaderElement element1 = new BasicHeaderElement("name1", "value1", param1); final HeaderElement element2 = new BasicHeaderElement("name2", "value2", param2); final HeaderElement element3 = new BasicHeaderElement("name3", "value3", param3); final HeaderElement element4 = new BasicHeaderElement("name4", "value4", param4); final HeaderElement element5 = new BasicHeaderElement("name5", null); final HeaderElement[] elements = new HeaderElement[] {element1, element2, element3, element4, element5}; final CharArrayBuffer buf = new CharArrayBuffer(64); this.formatter.formatElements(buf, elements, false); Assertions.assertEquals("name1=value1; param=regular_stuff, name2=value2; " + "param=\"this\\\\that\", name3=value3; param=\"this,that\", " + "name4=value4; param, name5", buf.toString()); } @Test void testInvalidArguments() { final CharArrayBuffer buf = new CharArrayBuffer(64); final NameValuePair param = new BasicNameValuePair("param", "regular_stuff"); final NameValuePair[] params = new NameValuePair[] {param}; final HeaderElement element = new BasicHeaderElement("name1", "value1", (NameValuePair[]) null); final HeaderElement[] elements = new HeaderElement[] {element}; Assertions.assertThrows(NullPointerException.class, () -> formatter.formatNameValuePair(null, param, false)); Assertions.assertThrows(NullPointerException.class, () -> formatter.formatNameValuePair(buf, null, false)); Assertions.assertThrows(NullPointerException.class, () -> formatter.formatParameters(null, params, false)); Assertions.assertThrows(NullPointerException.class, () -> formatter.formatParameters(buf, null, false)); Assertions.assertThrows(NullPointerException.class, () -> formatter.formatHeaderElement(null, element, false)); Assertions.assertThrows(NullPointerException.class, () -> formatter.formatHeaderElement(buf, null, false)); Assertions.assertThrows(NullPointerException.class, () -> formatter.formatElements(null, elements, false)); Assertions.assertThrows(NullPointerException.class, () -> formatter.formatElements(buf, null, false)); } } httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderValueParser.java0100644 0000000 0000000 00000032046 00000250600 030225 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.http.HeaderElement; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.util.CharArrayBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** * Tests for header value parsing. * * @version $Id$ */ class TestBasicHeaderValueParser { private BasicHeaderValueParser parser; @BeforeEach void setup() { this.parser = BasicHeaderValueParser.INSTANCE; } @Test void testParseHeaderElements() { final String headerValue = "name1 = value1; name2; name3=\"value3\" , name4=value4; " + "name5=value5, name6= ; name7 = value7; name8 = \" value8\""; final CharArrayBuffer buf = new CharArrayBuffer(64); buf.append(headerValue); final ParserCursor cursor = new ParserCursor(0, buf.length()); final HeaderElement[] elements = this.parser.parseElements(buf, cursor); // there are 3 elements Assertions.assertEquals(3,elements.length); // 1st element Assertions.assertEquals("name1",elements[0].getName()); Assertions.assertEquals("value1",elements[0].getValue()); // 1st element has 2 getParameters() Assertions.assertEquals(2,elements[0].getParameters().length); Assertions.assertEquals("name2",elements[0].getParameters()[0].getName()); Assertions.assertNull(elements[0].getParameters()[0].getValue()); Assertions.assertEquals("name3",elements[0].getParameters()[1].getName()); Assertions.assertEquals("value3",elements[0].getParameters()[1].getValue()); // 2nd element Assertions.assertEquals("name4",elements[1].getName()); Assertions.assertEquals("value4",elements[1].getValue()); // 2nd element has 1 parameter Assertions.assertEquals(1,elements[1].getParameters().length); Assertions.assertEquals("name5",elements[1].getParameters()[0].getName()); Assertions.assertEquals("value5",elements[1].getParameters()[0].getValue()); // 3rd element Assertions.assertEquals("name6",elements[2].getName()); Assertions.assertEquals("",elements[2].getValue()); // 3rd element has 2 getParameters() Assertions.assertEquals(2,elements[2].getParameters().length); Assertions.assertEquals("name7",elements[2].getParameters()[0].getName()); Assertions.assertEquals("value7",elements[2].getParameters()[0].getValue()); Assertions.assertEquals("name8",elements[2].getParameters()[1].getName()); Assertions.assertEquals(" value8",elements[2].getParameters()[1].getValue()); } @Test void testParseHEEscaped() { final String headerValue = "test1 = \"\\\"stuff\\\"\", test2= \"\\\\\", test3 = \"stuff, stuff\""; final CharArrayBuffer buf = new CharArrayBuffer(64); buf.append(headerValue); final ParserCursor cursor = new ParserCursor(0, buf.length()); final HeaderElement[] elements = this.parser.parseElements(buf, cursor); Assertions.assertEquals(3, elements.length); Assertions.assertEquals("test1", elements[0].getName()); Assertions.assertEquals("\"stuff\"", elements[0].getValue()); Assertions.assertEquals("test2", elements[1].getName()); Assertions.assertEquals("\\", elements[1].getValue()); Assertions.assertEquals("test3", elements[2].getName()); Assertions.assertEquals("stuff, stuff", elements[2].getValue()); } @Test void testHEFringeCase1() { final String headerValue = "name1 = value1,"; final CharArrayBuffer buf = new CharArrayBuffer(64); buf.append(headerValue); final ParserCursor cursor = new ParserCursor(0, buf.length()); final HeaderElement[] elements = this.parser.parseElements(buf, cursor); Assertions.assertEquals(1, elements.length, "Number of elements"); } @Test void testHEFringeCase2() { final String headerValue = "name1 = value1, "; final CharArrayBuffer buf = new CharArrayBuffer(64); buf.append(headerValue); final ParserCursor cursor = new ParserCursor(0, buf.length()); final HeaderElement[] elements = this.parser.parseElements(buf, cursor); Assertions.assertEquals(1, elements.length, "Number of elements"); } @Test void testHEFringeCase3() { final String headerValue = ",, ,, ,"; final CharArrayBuffer buf = new CharArrayBuffer(64); buf.append(headerValue); final ParserCursor cursor = new ParserCursor(0, buf.length()); final HeaderElement[] elements = this.parser.parseElements(buf, cursor); Assertions.assertEquals(0, elements.length, "Number of elements"); } @Test void testNVParse() { String s = "test"; CharArrayBuffer buffer = new CharArrayBuffer(64); buffer.append(s); ParserCursor cursor = new ParserCursor(0, s.length()); NameValuePair param = this.parser.parseNameValuePair(buffer, cursor); Assertions.assertEquals("test", param.getName()); Assertions.assertNull(param.getValue()); Assertions.assertEquals(s.length(), cursor.getPos()); Assertions.assertTrue(cursor.atEnd()); s = "test;"; buffer = new CharArrayBuffer(16); buffer.append(s); cursor = new ParserCursor(0, s.length()); param = this.parser.parseNameValuePair(buffer, cursor); Assertions.assertEquals("test", param.getName()); Assertions.assertNull(param.getValue()); Assertions.assertEquals(s.length() - 1, cursor.getPos()); Assertions.assertFalse(cursor.atEnd()); s = "test ,12"; buffer = new CharArrayBuffer(16); buffer.append(s); cursor = new ParserCursor(0, s.length()); param = this.parser.parseNameValuePair(buffer, cursor); Assertions.assertEquals("test", param.getName()); Assertions.assertNull(param.getValue()); Assertions.assertEquals(s.length() - 3, cursor.getPos()); Assertions.assertFalse(cursor.atEnd()); s = "test=stuff"; buffer = new CharArrayBuffer(16); buffer.append(s); cursor = new ParserCursor(0, s.length()); param = this.parser.parseNameValuePair(buffer, cursor); Assertions.assertEquals("test", param.getName()); Assertions.assertEquals("stuff", param.getValue()); Assertions.assertEquals(s.length(), cursor.getPos()); Assertions.assertTrue(cursor.atEnd()); s = " test = stuff "; buffer = new CharArrayBuffer(16); buffer.append(s); cursor = new ParserCursor(0, s.length()); param = this.parser.parseNameValuePair(buffer, cursor); Assertions.assertEquals("test", param.getName()); Assertions.assertEquals("stuff", param.getValue()); Assertions.assertEquals(s.length(), cursor.getPos()); Assertions.assertTrue(cursor.atEnd()); s = " test = stuff ;1234"; buffer = new CharArrayBuffer(16); buffer.append(s); cursor = new ParserCursor(0, s.length()); param = this.parser.parseNameValuePair(buffer, cursor); Assertions.assertEquals("test", param.getName()); Assertions.assertEquals("stuff", param.getValue()); Assertions.assertEquals(s.length() - 5, cursor.getPos()); Assertions.assertFalse(cursor.atEnd()); s = "test = \"stuff\""; buffer = new CharArrayBuffer(16); buffer.append(s); cursor = new ParserCursor(0, s.length()); param = this.parser.parseNameValuePair(buffer, cursor); Assertions.assertEquals("test", param.getName()); Assertions.assertEquals("stuff", param.getValue()); s = "test = \" stuff\\\"\""; buffer = new CharArrayBuffer(16); buffer.append(s); cursor = new ParserCursor(0, s.length()); param = this.parser.parseNameValuePair(buffer, cursor); Assertions.assertEquals("test", param.getName()); Assertions.assertEquals(" stuff\"", param.getValue()); s = " test"; buffer = new CharArrayBuffer(16); buffer.append(s); cursor = new ParserCursor(0, s.length()); param = this.parser.parseNameValuePair(buffer, cursor); Assertions.assertEquals("test", param.getName()); Assertions.assertNull(param.getValue()); s = " "; buffer = new CharArrayBuffer(16); buffer.append(s); cursor = new ParserCursor(0, s.length()); param = this.parser.parseNameValuePair(buffer, cursor); Assertions.assertEquals("", param.getName()); Assertions.assertNull(param.getValue()); s = " = stuff "; buffer = new CharArrayBuffer(16); buffer.append(s); cursor = new ParserCursor(0, s.length()); param = this.parser.parseNameValuePair(buffer, cursor); Assertions.assertEquals("", param.getName()); Assertions.assertEquals("stuff", param.getValue()); } @Test void testNVParseAll() { String s = "test; test1 = stuff ; test2 = \"stuff; stuff\"; test3=\"stuff"; CharArrayBuffer buffer = new CharArrayBuffer(16); buffer.append(s); ParserCursor cursor = new ParserCursor(0, s.length()); NameValuePair[] params = this.parser.parseParameters(buffer, cursor); Assertions.assertEquals("test", params[0].getName()); Assertions.assertNull(params[0].getValue()); Assertions.assertEquals("test1", params[1].getName()); Assertions.assertEquals("stuff", params[1].getValue()); Assertions.assertEquals("test2", params[2].getName()); Assertions.assertEquals("stuff; stuff", params[2].getValue()); Assertions.assertEquals("test3", params[3].getName()); Assertions.assertEquals("stuff", params[3].getValue()); Assertions.assertEquals(s.length(), cursor.getPos()); Assertions.assertTrue(cursor.atEnd()); s = "test; test1 = stuff ; test2 = \"stuff; stuff\"; test3=\"stuff\",123"; buffer = new CharArrayBuffer(16); buffer.append(s); cursor = new ParserCursor(0, s.length()); params = this.parser.parseParameters(buffer, cursor); Assertions.assertEquals("test", params[0].getName()); Assertions.assertNull(params[0].getValue()); Assertions.assertEquals("test1", params[1].getName()); Assertions.assertEquals("stuff", params[1].getValue()); Assertions.assertEquals("test2", params[2].getName()); Assertions.assertEquals("stuff; stuff", params[2].getValue()); Assertions.assertEquals("test3", params[3].getName()); Assertions.assertEquals("stuff", params[3].getValue()); Assertions.assertEquals(s.length() - 4, cursor.getPos()); Assertions.assertFalse(cursor.atEnd()); s = " "; buffer = new CharArrayBuffer(16); buffer.append(s); cursor = new ParserCursor(0, s.length()); params = this.parser.parseParameters(buffer, cursor); Assertions.assertEquals(0, params.length); } @Test void testNVParseEscaped() { final String headerValue = "test1 = \"\\\"stuff\\\"\"; test2= \"\\\\\"; test3 = \"stuff; stuff\""; final CharArrayBuffer buf = new CharArrayBuffer(64); buf.append(headerValue); final ParserCursor cursor = new ParserCursor(0, buf.length()); final NameValuePair[] params = this.parser.parseParameters(buf, cursor); Assertions.assertEquals(3, params.length); Assertions.assertEquals("test1", params[0].getName()); Assertions.assertEquals("\"stuff\"", params[0].getValue()); Assertions.assertEquals("test2", params[1].getName()); Assertions.assertEquals("\\", params[1].getValue()); Assertions.assertEquals("test3", params[2].getName()); Assertions.assertEquals("stuff; stuff", params[2].getValue()); } } httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicLineFormatter.java0100644 0000000 0000000 00000012342 00000250600 027433 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.util.CharArrayBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** * Tests for {@link BasicLineFormatter}. */ class TestBasicLineFormatter { private BasicLineFormatter formatter; @BeforeEach void setup() { this.formatter = BasicLineFormatter.INSTANCE; } @Test void testHttpVersionFormatting() { final CharArrayBuffer buf = new CharArrayBuffer(64); this.formatter.formatProtocolVersion(buf, HttpVersion.HTTP_1_1); Assertions.assertEquals("HTTP/1.1", buf.toString()); } @Test void testRLFormatting() { final RequestLine requestline = new RequestLine(Method.GET.name(), "/stuff", HttpVersion.HTTP_1_1); final CharArrayBuffer buf = new CharArrayBuffer(64); this.formatter.formatRequestLine(buf, requestline); Assertions.assertEquals("GET /stuff HTTP/1.1", buf.toString()); } @Test void testRLFormattingInvalidInput() { final CharArrayBuffer buf = new CharArrayBuffer(64); final RequestLine requestline = new RequestLine(Method.GET.name(), "/stuff", HttpVersion.HTTP_1_1); Assertions.assertThrows(NullPointerException.class, () -> formatter.formatRequestLine(null, requestline)); Assertions.assertThrows(NullPointerException.class, () -> formatter.formatRequestLine(buf, null)); } @Test void testSLFormatting() { final CharArrayBuffer buf = new CharArrayBuffer(64); final StatusLine statusline1 = new StatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); this.formatter.formatStatusLine(buf, statusline1); Assertions.assertEquals("HTTP/1.1 200 OK", buf.toString()); buf.clear(); final StatusLine statusline2 = new StatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, null); this.formatter.formatStatusLine(buf, statusline2); Assertions.assertEquals("HTTP/1.1 200 ", buf.toString()); // see "testSLParseSuccess" in TestBasicLineParser: // trailing space is correct } @Test void testSLFormattingInvalidInput() { final CharArrayBuffer buf = new CharArrayBuffer(64); final StatusLine statusline = new StatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); Assertions.assertThrows(NullPointerException.class, () -> formatter.formatStatusLine(null, statusline)); Assertions.assertThrows(NullPointerException.class, () -> formatter.formatStatusLine(buf, null)); } @Test void testHeaderFormatting() { final CharArrayBuffer buf = new CharArrayBuffer(64); final Header header1 = new BasicHeader("name", "value"); this.formatter.formatHeader(buf, header1); Assertions.assertEquals("name: value", buf.toString()); buf.clear(); final Header header2 = new BasicHeader("name", null); this.formatter.formatHeader(buf, header2); Assertions.assertEquals("name: ", buf.toString()); } @Test void testHeaderFormattingInvalidInput() { final CharArrayBuffer buf = new CharArrayBuffer(64); final Header header = new BasicHeader("name", "value"); Assertions.assertThrows(NullPointerException.class, () -> formatter.formatHeader(null, header)); Assertions.assertThrows(NullPointerException.class, () -> formatter.formatHeader(buf, null)); } @Test void testHeaderFormattingRequestSplitting() { final CharArrayBuffer buf = new CharArrayBuffer(64); final Header header = new BasicHeader("Host", "apache.org\r\nOops: oops"); formatter.formatHeader(buf, header); final String s = buf.toString(); Assertions.assertFalse(s.contains("\n")); Assertions.assertEquals("Host: apache.org Oops: oops", s); } } httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicLineParser.java0100644 0000000 0000000 00000033144 00000250600 026727 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.util.CharArrayBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** * Tests for {@link BasicLineParser}. * */ class TestBasicLineParser { private BasicLineParser parser; @BeforeEach void setup() { this.parser = BasicLineParser.INSTANCE; } @Test void testRLParse() throws Exception { final CharArrayBuffer buf = new CharArrayBuffer(64); //typical request line buf.clear(); buf.append("GET /stuff HTTP/1.1"); RequestLine requestline = this.parser.parseRequestLine(buf); Assertions.assertEquals("GET /stuff HTTP/1.1", requestline.toString()); Assertions.assertEquals(Method.GET.name(), requestline.getMethod()); Assertions.assertEquals("/stuff", requestline.getUri()); Assertions.assertEquals(HttpVersion.HTTP_1_1, requestline.getProtocolVersion()); //Lots of blanks buf.clear(); buf.append(" GET /stuff HTTP/1.1 "); requestline = this.parser.parseRequestLine(buf); Assertions.assertEquals("GET /stuff HTTP/1.1", requestline.toString()); Assertions.assertEquals(Method.GET.name(), requestline.getMethod()); Assertions.assertEquals("/stuff", requestline.getUri()); Assertions.assertEquals(HttpVersion.HTTP_1_1, requestline.getProtocolVersion()); //this is not strictly valid, but is lenient buf.clear(); buf.append("\rGET /stuff HTTP/1.1"); requestline = this.parser.parseRequestLine(buf); Assertions.assertEquals(Method.GET.name(), requestline.getMethod()); Assertions.assertEquals("/stuff", requestline.getUri()); Assertions.assertEquals(HttpVersion.HTTP_1_1, requestline.getProtocolVersion()); } @Test void testRLParseFailure() { final CharArrayBuffer buf = new CharArrayBuffer(64); buf.clear(); buf.append(" "); Assertions.assertThrows(ParseException.class, () -> parser.parseRequestLine(buf)); buf.clear(); buf.append(" GET"); Assertions.assertThrows(ParseException.class, () -> parser.parseRequestLine(buf)); buf.clear(); buf.append("GET /stuff"); Assertions.assertThrows(ParseException.class, () -> parser.parseRequestLine(buf)); buf.clear(); buf.append("GET/stuff HTTP/1.1"); Assertions.assertThrows(ParseException.class, () -> parser.parseRequestLine(buf)); buf.clear(); buf.append("GET /stuff HTTP/1.1 Oooooooooooppsie"); Assertions.assertThrows(ParseException.class, () -> parser.parseRequestLine(buf)); } @Test void testSLParse() throws Exception { final CharArrayBuffer buf = new CharArrayBuffer(64); //typical status line buf.clear(); buf.append("HTTP/1.1 200 OK"); StatusLine statusLine = this.parser.parseStatusLine(buf); Assertions.assertEquals("HTTP/1.1 200 OK", statusLine.toString()); Assertions.assertEquals(HttpVersion.HTTP_1_1, statusLine.getProtocolVersion()); Assertions.assertEquals(200, statusLine.getStatusCode()); Assertions.assertEquals("OK", statusLine.getReasonPhrase()); //status line with multi word reason phrase buf.clear(); buf.append("HTTP/1.1 404 Not Found"); statusLine = this.parser.parseStatusLine(buf); Assertions.assertEquals(404, statusLine.getStatusCode()); Assertions.assertEquals("Not Found", statusLine.getReasonPhrase()); //reason phrase can be anyting buf.clear(); buf.append("HTTP/1.1 404 Non Trouve"); statusLine = this.parser.parseStatusLine(buf); Assertions.assertEquals("Non Trouve", statusLine.getReasonPhrase()); //its ok to end with a \n\r buf.clear(); buf.append("HTTP/1.1 404 Not Found\r\n"); statusLine = this.parser.parseStatusLine(buf); Assertions.assertEquals("Not Found", statusLine.getReasonPhrase()); //this is valid according to the Status-Line BNF buf.clear(); buf.append("HTTP/1.1 200 "); statusLine = this.parser.parseStatusLine(buf); Assertions.assertEquals(200, statusLine.getStatusCode()); Assertions.assertEquals("", statusLine.getReasonPhrase()); //this is not strictly valid, but is lenient buf.clear(); buf.append("HTTP/1.1 200"); statusLine = this.parser.parseStatusLine(buf); Assertions.assertEquals(200, statusLine.getStatusCode()); Assertions.assertEquals("", statusLine.getReasonPhrase()); //this is not strictly valid, but is lenient buf.clear(); buf.append("HTTP/1.1 200 OK"); statusLine = this.parser.parseStatusLine(buf); Assertions.assertEquals(200, statusLine.getStatusCode()); Assertions.assertEquals("OK", statusLine.getReasonPhrase()); //this is not strictly valid, but is lenient buf.clear(); buf.append("\nHTTP/1.1 200 OK"); statusLine = this.parser.parseStatusLine(buf); Assertions.assertEquals(200, statusLine.getStatusCode()); Assertions.assertEquals("OK", statusLine.getReasonPhrase()); Assertions.assertEquals(HttpVersion.HTTP_1_1, statusLine.getProtocolVersion()); //this is not strictly valid, but is lenient buf.clear(); buf.append(" HTTP/1.1 200 OK"); statusLine = this.parser.parseStatusLine(buf); Assertions.assertEquals(200, statusLine.getStatusCode()); Assertions.assertEquals("OK", statusLine.getReasonPhrase()); Assertions.assertEquals(HttpVersion.HTTP_1_1, statusLine.getProtocolVersion()); } @Test void testSLParseFailure() { final CharArrayBuffer buf = new CharArrayBuffer(64); buf.clear(); buf.append("xxx 200 OK"); Assertions.assertThrows(ParseException.class, () -> parser.parseStatusLine(buf)); buf.clear(); buf.append("HTTP/1.1 xxx OK"); Assertions.assertThrows(ParseException.class, () -> parser.parseStatusLine(buf)); buf.clear(); buf.append("HTTP/1.1 "); Assertions.assertThrows(ParseException.class, () -> parser.parseStatusLine(buf)); buf.clear(); buf.append("HTTP/1.1"); Assertions.assertThrows(ParseException.class, () -> parser.parseStatusLine(buf)); buf.clear(); buf.append("HTTP/1.1 -200 OK"); Assertions.assertThrows(ParseException.class, () -> parser.parseStatusLine(buf)); buf.clear(); buf.append("HTTP/1.1 0200 OK"); Assertions.assertThrows(ParseException.class, () -> parser.parseStatusLine(buf)); buf.clear(); buf.append("HTTP/1.1 2000 OK"); Assertions.assertThrows(ParseException.class, () -> parser.parseStatusLine(buf)); } @Test void testHttpVersionParsing() throws Exception { final CharArrayBuffer buffer = new CharArrayBuffer(16); buffer.append("HTTP/1.1"); final ParserCursor cursor = new ParserCursor(0, buffer.length()); final ProtocolVersion version = parser.parseProtocolVersion(buffer, cursor); Assertions.assertEquals("HTTP", version.getProtocol(), "HTTP protocol name"); Assertions.assertEquals(1, version.getMajor(), "HTTP major version number"); Assertions.assertEquals(1, version.getMinor(), "HTTP minor version number"); Assertions.assertEquals("HTTP/1.1", version.toString(), "HTTP version number"); Assertions.assertEquals(buffer.length(), cursor.getPos()); Assertions.assertTrue(cursor.atEnd()); buffer.clear(); buffer.append("HTTP/1.123 123"); final ParserCursor cursor2 = new ParserCursor(0, buffer.length()); final ProtocolVersion version2 = parser.parseProtocolVersion(buffer, cursor2); Assertions.assertEquals( "HTTP", version2.getProtocol(), "HTTP protocol name"); Assertions.assertEquals( 1, version2.getMajor(), "HTTP major version number"); Assertions.assertEquals(123, version2.getMinor(), "HTTP minor version number"); Assertions.assertEquals("HTTP/1.123", version2.toString(), "HTTP version number"); Assertions.assertEquals(' ', buffer.charAt(cursor2.getPos())); Assertions.assertEquals(buffer.length() - 4, cursor2.getPos()); Assertions.assertFalse(cursor2.atEnd()); } @Test void testInvalidHttpVersionParsing() { final CharArrayBuffer buffer = new CharArrayBuffer(16); buffer.clear(); buffer.append(" "); final ParserCursor cursor1 = new ParserCursor(0, buffer.length()); Assertions.assertThrows(ParseException.class, () -> parser.parseProtocolVersion(buffer, cursor1)); buffer.clear(); buffer.append("HTT"); final ParserCursor cursor2 = new ParserCursor(0, buffer.length()); Assertions.assertThrows(ParseException.class, () -> parser.parseProtocolVersion(buffer, cursor2)); buffer.clear(); buffer.append("crap"); final ParserCursor cursor3 = new ParserCursor(0, buffer.length()); Assertions.assertThrows(ParseException.class, () -> parser.parseProtocolVersion(buffer, cursor3)); buffer.clear(); buffer.append("HTTP/crap"); final ParserCursor cursor4 = new ParserCursor(0, buffer.length()); Assertions.assertThrows(ParseException.class, () -> parser.parseProtocolVersion(buffer, cursor4)); buffer.clear(); buffer.append("HTTP/1"); final ParserCursor cursor5 = new ParserCursor(0, buffer.length()); Assertions.assertThrows(ParseException.class, () -> parser.parseProtocolVersion(buffer, cursor5)); buffer.clear(); buffer.append("HTTP/1."); final ParserCursor cursor7 = new ParserCursor(0, buffer.length()); Assertions.assertThrows(ParseException.class, () -> parser.parseProtocolVersion(buffer, cursor7)); buffer.clear(); buffer.append("HTTP/whatever.whatever whatever"); final ParserCursor cursor8 = new ParserCursor(0, buffer.length()); Assertions.assertThrows(ParseException.class, () -> parser.parseProtocolVersion(buffer, cursor8)); buffer.clear(); buffer.append("HTTP/1.whatever whatever"); final ParserCursor cursor9 = new ParserCursor(0, buffer.length()); Assertions.assertThrows(ParseException.class, () -> parser.parseProtocolVersion(buffer, cursor9)); } @Test void testHeaderParse() throws Exception { final CharArrayBuffer buf = new CharArrayBuffer(64); //typical request line buf.clear(); buf.append("header: blah"); Header header = this.parser.parseHeader(buf); Assertions.assertEquals("header", header.getName()); Assertions.assertEquals("blah", header.getValue()); //Lots of blanks buf.clear(); buf.append(" header: blah "); header = this.parser.parseHeader(buf); Assertions.assertEquals("header", header.getName()); Assertions.assertEquals("blah", header.getValue()); } @Test void testInvalidHeaderParsing() { final CharArrayBuffer buffer = new CharArrayBuffer(16); buffer.clear(); buffer.append(""); Assertions.assertThrows(ParseException.class, () -> parser.parseHeader(buffer)); buffer.clear(); buffer.append("blah"); Assertions.assertThrows(ParseException.class, () -> parser.parseHeader(buffer)); buffer.clear(); buffer.append(":"); Assertions.assertThrows(ParseException.class, () -> parser.parseHeader(buffer)); buffer.clear(); buffer.append(" :"); Assertions.assertThrows(ParseException.class, () -> parser.parseHeader(buffer)); buffer.clear(); buffer.append(": blah"); Assertions.assertThrows(ParseException.class, () -> parser.parseHeader(buffer)); buffer.clear(); buffer.append(" : blah"); Assertions.assertThrows(ParseException.class, () -> parser.parseHeader(buffer)); buffer.clear(); buffer.append("header : blah"); Assertions.assertThrows(ParseException.class, () -> parser.parseHeader(buffer)); } } httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicMessages.java0100644 0000000 0000000 00000024176 00000250600 026437 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.net.URI; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.net.URIBuilder; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link org.apache.hc.core5.http.HttpMessage}. * */ class TestBasicMessages { @Test void testDefaultResponseConstructors() { final HttpResponse response1 = new BasicHttpResponse(HttpStatus.SC_BAD_REQUEST, "Bad Request"); Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, response1.getCode()); final HttpResponse response2 = new BasicHttpResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR, "whatever"); Assertions.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response2.getCode()); Assertions.assertEquals("whatever", response2.getReasonPhrase()); } @Test void testSetResponseStatus() { final HttpResponse response1 = new BasicHttpResponse(200, "OK"); Assertions.assertEquals(200, response1.getCode()); final HttpResponse response2 = new BasicHttpResponse(HttpStatus.SC_BAD_REQUEST, "Bad Request"); Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, response2.getCode()); final HttpResponse response3 = new BasicHttpResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR, "whatever"); Assertions.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response3.getCode()); Assertions.assertEquals("whatever", response3.getReasonPhrase()); final HttpResponse response4 = new BasicHttpResponse(HttpStatus.SC_OK, "OK"); Assertions.assertThrows(IllegalArgumentException.class, () -> response4.setCode(-23)); } @Test void testDefaultRequestConstructors() { final HttpRequest request1 = new BasicHttpRequest("WHATEVER", "/"); Assertions.assertEquals("WHATEVER", request1.getMethod()); Assertions.assertEquals("/", request1.getPath()); final HttpRequest request2 = new BasicHttpRequest(Method.GET, "/"); Assertions.assertEquals(Method.GET.name(), request2.getMethod()); Assertions.assertEquals("/", request2.getPath()); Assertions.assertThrows(NullPointerException.class, () -> new BasicHttpRequest(Method.GET, (URI) null)); } @Test void testResponseBasics() { final BasicHttpResponse response = new BasicHttpResponse(200, "OK"); Assertions.assertEquals(200, response.getCode()); Assertions.assertEquals("OK", response.getReasonPhrase()); } @Test void testResponseStatusLineMutation() { final BasicHttpResponse response = new BasicHttpResponse(200, "OK"); Assertions.assertEquals(200, response.getCode()); Assertions.assertEquals("OK", response.getReasonPhrase()); response.setReasonPhrase("Kind of OK"); Assertions.assertEquals(200, response.getCode()); Assertions.assertEquals("Kind of OK", response.getReasonPhrase()); response.setCode(299); Assertions.assertEquals(299, response.getCode()); Assertions.assertNull(response.getReasonPhrase()); } @Test void testResponseInvalidStatusCode() { Assertions.assertThrows(IllegalArgumentException.class, () -> new BasicHttpResponse(-200, "OK")); final BasicHttpResponse response = new BasicHttpResponse(200, "OK"); Assertions.assertThrows(IllegalArgumentException.class, () -> response.setCode(-1)); } @Test void testRequestBasics() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, "/stuff"); Assertions.assertEquals(Method.GET.name(), request.getMethod()); Assertions.assertEquals("/stuff", request.getPath()); Assertions.assertNull(request.getAuthority()); Assertions.assertEquals(new URI("/stuff"), request.getUri()); } @Test void testRequestWithRelativeURI() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, new URI("/stuff")); Assertions.assertEquals(Method.GET.name(), request.getMethod()); Assertions.assertEquals("/stuff", request.getPath()); Assertions.assertNull(request.getAuthority()); Assertions.assertEquals(new URI("/stuff"), request.getUri()); } @Test void testRequestWithAbsoluteURI() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, new URI("https://host:9443/stuff?param=value")); Assertions.assertEquals(Method.GET.name(), request.getMethod()); Assertions.assertEquals("/stuff?param=value", request.getPath()); Assertions.assertEquals(new URIAuthority("host", 9443), request.getAuthority()); Assertions.assertEquals("https", request.getScheme()); Assertions.assertEquals(new URI("https://host:9443/stuff?param=value"), request.getUri()); } @Test void testRequestWithAbsoluteURIAsPath() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, "https://host:9443/stuff?param=value"); Assertions.assertEquals(Method.GET.name(), request.getMethod()); Assertions.assertEquals("/stuff?param=value", request.getPath()); Assertions.assertEquals(new URIAuthority("host", 9443), request.getAuthority()); Assertions.assertEquals("https", request.getScheme()); Assertions.assertEquals(new URI("https://host:9443/stuff?param=value"), request.getUri()); } @Test void testRequestWithNoPath() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, new URI("http://host")); Assertions.assertEquals(Method.GET.name(), request.getMethod()); Assertions.assertEquals("/", request.getPath()); Assertions.assertEquals(new URIAuthority("host"), request.getAuthority()); Assertions.assertEquals("http", request.getScheme()); Assertions.assertEquals(new URI("http://host/"), request.getUri()); } @Test void testRequestWithUserInfo() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, new URI("https://user:pwd@host:9443/stuff?param=value")); Assertions.assertEquals(Method.GET.name(), request.getMethod()); Assertions.assertEquals("/stuff?param=value", request.getPath()); Assertions.assertEquals(new URIAuthority("user:pwd", "host", 9443), request.getAuthority()); Assertions.assertEquals("https", request.getScheme()); Assertions.assertEquals(new URI("https://host:9443/stuff?param=value"), request.getUri()); } @Test void testRequestWithAuthority() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, new HttpHost("http", "somehost", -1), "/stuff"); Assertions.assertEquals(Method.GET.name(), request.getMethod()); Assertions.assertEquals("/stuff", request.getPath()); Assertions.assertEquals(new URIAuthority("somehost"), request.getAuthority()); Assertions.assertEquals(new URI("http://somehost/stuff"), request.getUri()); } @Test void testRequestWithAuthorityRelativePath() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, new HttpHost("http", "somehost", -1), "stuff"); Assertions.assertEquals(Method.GET.name(), request.getMethod()); Assertions.assertEquals("stuff", request.getPath()); Assertions.assertEquals(new URIAuthority("somehost"), request.getAuthority()); Assertions.assertEquals(new URI("http://somehost/stuff"), request.getUri()); } @Test void testRequestHostWithReservedChars() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, URI.create("http://someuser%21@%21example%21.com/stuff")); Assertions.assertEquals(Method.GET.name(), request.getMethod()); Assertions.assertEquals("/stuff", request.getPath()); Assertions.assertEquals(new URIAuthority("someuser%21", "%21example%21.com", -1), request.getAuthority()); Assertions.assertEquals(new URI("http://%21example%21.com/stuff"), request.getUri()); } @Test void testRequestAbsoluteRequestUri() { final BasicHttpRequest request = new BasicHttpRequest(Method.GET, new HttpHost("http", "somehost", -1), "stuff"); Assertions.assertEquals("stuff", request.getRequestUri()); request.setAbsoluteRequestUri(true); Assertions.assertEquals("http://somehost/stuff", request.getRequestUri()); } @Test void testModifyingExistingRequest() throws Exception { final URI uri = URI.create("https://example.org"); final HttpRequest request = new BasicHttpRequest(Method.GET, uri); final URI newUri = new URIBuilder(request.getUri()) .addParameter("name", "value") .build(); request.setUri(newUri); Assertions.assertEquals(newUri, request.getUri()); } } httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicStatusLine.java0100644 0000000 0000000 00000007367 00000250600 026766 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.http.message.StatusLine.StatusClass; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link org.apache.hc.core5.http.message.StatusLine}. */ class TestBasicStatusLine { @Test void testGetStatusClass() { StatusLine statusLine = new StatusLine(new BasicHttpResponse(100, "Continue")); Assertions.assertEquals(StatusClass.INFORMATIONAL, statusLine.getStatusClass()); statusLine = new StatusLine(new BasicHttpResponse(200, "OK")); Assertions.assertEquals(StatusClass.SUCCESSFUL, statusLine.getStatusClass()); statusLine = new StatusLine(new BasicHttpResponse(302, "Found")); Assertions.assertEquals(StatusClass.REDIRECTION, statusLine.getStatusClass()); statusLine = new StatusLine(new BasicHttpResponse(409, "Conflict")); Assertions.assertEquals(StatusClass.CLIENT_ERROR, statusLine.getStatusClass()); statusLine = new StatusLine(new BasicHttpResponse(502, "Bad Gateway")); Assertions.assertEquals(StatusClass.SERVER_ERROR, statusLine.getStatusClass()); statusLine = new StatusLine(new BasicHttpResponse(999, "Not a status")); Assertions.assertEquals(StatusClass.OTHER, statusLine.getStatusClass()); } @Test void testGetStatusShorthand() { StatusLine statusLine = new StatusLine(new BasicHttpResponse(100, "Continue")); Assertions.assertTrue(statusLine.isInformational()); Assertions.assertFalse(statusLine.isSuccessful()); Assertions.assertFalse(statusLine.isError()); statusLine = new StatusLine(new BasicHttpResponse(200, "OK")); Assertions.assertTrue(statusLine.isSuccessful()); Assertions.assertFalse(statusLine.isRedirection()); Assertions.assertFalse(statusLine.isError()); statusLine = new StatusLine(new BasicHttpResponse(302, "Found")); Assertions.assertTrue(statusLine.isRedirection()); Assertions.assertFalse(statusLine.isClientError()); Assertions.assertFalse(statusLine.isError()); statusLine = new StatusLine(new BasicHttpResponse(409, "Conflict")); Assertions.assertTrue(statusLine.isClientError()); Assertions.assertTrue(statusLine.isError()); Assertions.assertFalse(statusLine.isServerError()); statusLine = new StatusLine(new BasicHttpResponse(502, "Bad Gateway")); Assertions.assertTrue(statusLine.isServerError()); Assertions.assertTrue(statusLine.isError()); Assertions.assertFalse(statusLine.isSuccessful()); } } httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicTokenIterator.java0100644 0000000 0000000 00000016407 00000250600 027460 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.util.Iterator; import java.util.NoSuchElementException; import org.apache.hc.core5.http.Header; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Tests for {@link BasicTokenIterator}. */ class TestBasicTokenIterator { @Test void testSingleHeader() { Header[] headers = new Header[]{ new BasicHeader("Name", "token0,token1, token2 , token3") }; Iterator
hit = new BasicHeaderIterator(headers, null); Iterator ti = new BasicTokenIterator(hit); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token0", ti.next()); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token1", ti.next()); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token2", ti.next()); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token3", ti.next()); Assertions.assertFalse(ti.hasNext()); headers = new Header[]{ new BasicHeader("Name", "token0") }; hit = new BasicHeaderIterator(headers, null); ti = new BasicTokenIterator(hit); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token0", ti.next()); Assertions.assertFalse(ti.hasNext()); } @Test void testMultiHeader() { final Header[] headers = new Header[]{ new BasicHeader("Name", "token0,token1"), new BasicHeader("Name", ""), new BasicHeader("Name", "token2"), new BasicHeader("Name", " "), new BasicHeader("Name", "token3 "), new BasicHeader("Name", ","), new BasicHeader("Name", "token4"), }; final Iterator
hit = new BasicHeaderIterator(headers, null); final Iterator ti = new BasicTokenIterator(hit); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token0", ti.next()); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token1", ti.next()); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token2", ti.next()); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token3", ti.next()); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token4", ti.next()); Assertions.assertFalse(ti.hasNext()); } @Test void testEmpty() { final Header[] headers = new Header[]{ new BasicHeader("Name", " "), new BasicHeader("Name", ""), new BasicHeader("Name", ","), new BasicHeader("Name", " ,, "), }; Iterator
hit = new BasicHeaderIterator(headers, null); Iterator ti = new BasicTokenIterator(hit); Assertions.assertFalse(ti.hasNext()); hit = new BasicHeaderIterator(headers, "empty"); ti = new BasicTokenIterator(hit); Assertions.assertFalse(ti.hasNext()); } @Test void testValueStart() { final Header[] headers = new Header[]{ new BasicHeader("Name", "token0"), new BasicHeader("Name", " token1"), new BasicHeader("Name", ",token2"), new BasicHeader("Name", " ,token3"), new BasicHeader("Name", ", token4"), new BasicHeader("Name", " , token5"), }; final Iterator
hit = new BasicHeaderIterator(headers, null); final Iterator ti = new BasicTokenIterator(hit); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token0", ti.next()); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token1", ti.next()); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token2", ti.next()); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token3", ti.next()); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token4", ti.next()); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token5", ti.next()); Assertions.assertFalse(ti.hasNext()); } @Test void testValueEnd() { final Header[] headers = new Header[]{ new BasicHeader("Name", "token0"), new BasicHeader("Name", "token1 "), new BasicHeader("Name", "token2,"), new BasicHeader("Name", "token3 ,"), new BasicHeader("Name", "token4, "), new BasicHeader("Name", "token5 , "), }; final Iterator
hit = new BasicHeaderIterator(headers, null); final Iterator ti = new BasicTokenIterator(hit); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token0", ti.next()); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token1", ti.next()); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token2", ti.next()); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token3", ti.next()); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token4", ti.next()); Assertions.assertTrue(ti.hasNext()); Assertions.assertEquals("token5", ti.next()); Assertions.assertFalse(ti.hasNext()); } @Test void testWrongPublic() { Assertions.assertThrows(NullPointerException.class, () -> new BasicTokenIterator(null)); final Header[] headers = new Header[]{ new BasicHeader("Name", " "), new BasicHeader("Name", ""), new BasicHeader("Name", ","), new BasicHeader("Name", " ,, "), }; final Iterator
hit = new BasicHeaderIterator(headers, null); final Iterator ti = new BasicTokenIterator(hit); Assertions.assertThrows(NoSuchElementException.class, () -> ti.next()); Assertions.assertThrows(UnsupportedOperationException.class, () -> ti.remove()); } } httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBufferedHeader.java0100644 0000000 0000000 00000011574 00000250600 026557 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.util.CharArrayBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link BufferedHeader}. * */ class TestBufferedHeader { @Test void testBasicConstructor() throws Exception { final CharArrayBuffer buf = new CharArrayBuffer(32); buf.append("name: value"); final BufferedHeader header = new BufferedHeader(buf, false); Assertions.assertEquals("name", header.getName()); Assertions.assertEquals("value", header.getValue()); Assertions.assertSame(buf, header.getBuffer()); Assertions.assertEquals(5, header.getValuePos()); } @Test void testSerialization() throws Exception { final CharArrayBuffer buf = new CharArrayBuffer(32); buf.append("name: value"); final BufferedHeader orig = new BufferedHeader(buf, false); final ByteArrayOutputStream outbuffer = new ByteArrayOutputStream(); try (ObjectOutputStream outStream = new ObjectOutputStream(outbuffer)) { outStream.writeObject(orig); } final byte[] raw = outbuffer.toByteArray(); final ByteArrayInputStream inBuffer = new ByteArrayInputStream(raw); final ObjectInputStream inStream = new ObjectInputStream(inBuffer); final BufferedHeader clone = (BufferedHeader) inStream.readObject(); Assertions.assertEquals(orig.getName(), clone.getName()); Assertions.assertEquals(orig.getValue(), clone.getValue()); } @Test void testInvalidHeaderParsing() { final CharArrayBuffer buf = new CharArrayBuffer(16); buf.clear(); buf.append(""); Assertions.assertThrows(ParseException.class, () -> new BufferedHeader(buf, false)); buf.clear(); buf.append("blah"); Assertions.assertThrows(ParseException.class, () -> new BufferedHeader(buf, false)); buf.clear(); buf.append(":"); Assertions.assertThrows(ParseException.class, () -> new BufferedHeader(buf, false)); buf.clear(); buf.append(" :"); Assertions.assertThrows(ParseException.class, () -> new BufferedHeader(buf, false)); buf.clear(); buf.append(": blah"); Assertions.assertThrows(ParseException.class, () -> new BufferedHeader(buf, false)); buf.clear(); buf.append(" : blah"); Assertions.assertThrows(ParseException.class, () -> new BufferedHeader(buf, false)); buf.clear(); buf.append("header : blah"); Assertions.assertThrows(ParseException.class, () -> new BufferedHeader(buf, true)); } @Test void testCRLFNullInHeaderValue() throws Exception { final CharArrayBuffer buf = new CharArrayBuffer(16); buf.clear(); buf.append("name: blah\0blah "); final BufferedHeader header1 = new BufferedHeader(buf, false); Assertions.assertEquals("name", header1.getName()); Assertions.assertEquals("blah blah", header1.getValue()); buf.clear(); buf.append("name: blah\rblah "); final BufferedHeader header2 = new BufferedHeader(buf, false); Assertions.assertEquals("name", header2.getName()); Assertions.assertEquals("blah blah", header2.getValue()); buf.clear(); buf.append("name: blah\nblah "); final BufferedHeader header3 = new BufferedHeader(buf, false); Assertions.assertEquals("name", header3.getName()); Assertions.assertEquals("blah blah", header3.getValue()); } } httpcore5/src/test/java/org/apache/hc/core5/http/message/TestHeader.java0100644 0000000 0000000 00000006713 00000250600 025113 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import org.apache.hc.core5.http.Header; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link Header}. */ class TestHeader { @Test void testBasicConstructor() { final Header header = new BasicHeader("name", "value"); Assertions.assertEquals("name", header.getName()); Assertions.assertEquals("value", header.getValue()); } @Test void testBasicConstructorNullValue() { final Header header = new BasicHeader("name", null); Assertions.assertEquals("name", header.getName()); Assertions.assertNull(header.getValue()); } @Test void testInvalidName() { Assertions.assertThrows(NullPointerException.class, () -> new BasicHeader(null, null)); } @Test void testToString() { final Header header1 = new BasicHeader("name1", "value1"); Assertions.assertEquals("name1: value1", header1.toString()); final Header header2 = new BasicHeader("name2", null); Assertions.assertEquals("name2: ", header2.toString()); } @Test void testSerialization() throws Exception { final BasicHeader orig = new BasicHeader("name1", "value1"); final ByteArrayOutputStream outbuffer = new ByteArrayOutputStream(); try (ObjectOutputStream outStream = new ObjectOutputStream(outbuffer)) { outStream.writeObject(orig); } final byte[] raw = outbuffer.toByteArray(); final ByteArrayInputStream inBuffer = new ByteArrayInputStream(raw); final ObjectInputStream inStream = new ObjectInputStream(inBuffer); final BasicHeader clone = (BasicHeader) inStream.readObject(); Assertions.assertEquals(orig.getName(), clone.getName()); Assertions.assertEquals(orig.getValue(), clone.getValue()); } @Test void testClone() throws Exception { final BasicHeader orig = new BasicHeader("name1", "value1"); final BasicHeader clone = orig.clone(); Assertions.assertEquals(orig.getName(), clone.getName()); Assertions.assertEquals(orig.getValue(), clone.getValue()); } } httpcore5/src/test/java/org/apache/hc/core5/http/message/TestHeaderElement.java0100644 0000000 0000000 00000007203 00000250600 026420 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.http.HeaderElement; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.util.CharArrayBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Simple tests for {@link HeaderElement}. */ class TestHeaderElement { @Test void testConstructor3() { final HeaderElement element = new BasicHeaderElement("name", "value", new BasicNameValuePair("param1", "value1"), new BasicNameValuePair("param2", "value2")); Assertions.assertEquals("name", element.getName()); Assertions.assertEquals("value", element.getValue()); Assertions.assertEquals(2, element.getParameters().length); Assertions.assertEquals("value1", element.getParameterByName("param1").getValue()); Assertions.assertEquals("value2", element.getParameterByName("param2").getValue()); } @Test void testConstructor2() { final HeaderElement element = new BasicHeaderElement("name", "value"); Assertions.assertEquals("name", element.getName()); Assertions.assertEquals("value", element.getValue()); Assertions.assertEquals(0, element.getParameters().length); } @Test void testInvalidName() { Assertions.assertThrows(NullPointerException.class, () -> new BasicHeaderElement(null, null, (NameValuePair[]) null)); } @Test void testParamByName() { final String s = "name = value; param1 = value1; param2 = value2"; final CharArrayBuffer buf = new CharArrayBuffer(64); buf.append(s); final ParserCursor cursor = new ParserCursor(0, buf.length()); final HeaderElement element = BasicHeaderValueParser.INSTANCE.parseHeaderElement(buf, cursor); Assertions.assertEquals("value1", element.getParameterByName("param1").getValue()); Assertions.assertEquals("value2", element.getParameterByName("param2").getValue()); Assertions.assertNull(element.getParameterByName("param3")); Assertions.assertThrows(NullPointerException.class, () -> element.getParameterByName(null)); } @Test void testToString() { final BasicHeaderElement element = new BasicHeaderElement("name", "value", new BasicNameValuePair("param1", "value1"), new BasicNameValuePair("param2", "value2")); Assertions.assertEquals("name=value; param1=value1; param2=value2", element.toString()); } } httpcore5/src/test/java/org/apache/hc/core5/http/message/TestHeaderGroup.java0100644 0000000 0000000 00000022243 00000250600 026124 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Iterator; import org.apache.hc.core5.http.Header; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link HeaderGroup}. * */ class TestHeaderGroup { @Test void testConstructor() { final HeaderGroup headergroup = new HeaderGroup(); Assertions.assertNotNull(headergroup.getHeaders()); Assertions.assertEquals(0, headergroup.getHeaders().length); } @Test void testClear() { final HeaderGroup headergroup = new HeaderGroup(); headergroup.addHeader(new BasicHeader("name", "value")); Assertions.assertEquals(1, headergroup.getHeaders().length); headergroup.clear(); Assertions.assertEquals(0, headergroup.getHeaders().length); } @Test void testAddRemoveHeader() { final HeaderGroup headerGroup = new HeaderGroup(); final Header header = new BasicHeader("name", "value"); headerGroup.addHeader(header); headerGroup.addHeader(null); Assertions.assertEquals(1, headerGroup.getHeaders().length); Assertions.assertTrue(headerGroup.removeHeader(header)); Assertions.assertFalse(headerGroup.removeHeader(null)); Assertions.assertEquals(0, headerGroup.getHeaders().length); } @Test void testAddRemoveHeaders() { final HeaderGroup headergroup = new HeaderGroup(); final Header header = new BasicHeader("name", "value"); headergroup.addHeader(header); headergroup.addHeader(header); Assertions.assertEquals(2, headergroup.getHeaders().length); Assertions.assertFalse(headergroup.removeHeaders((Header) null)); Assertions.assertTrue(headergroup.removeHeaders(header)); Assertions.assertFalse(headergroup.removeHeaders((Header) null)); Assertions.assertEquals(0, headergroup.getHeaders().length); } @Test void testAddRemoveHeaderWithDifferentButEqualHeaders() { final HeaderGroup headergroup = new HeaderGroup(); final Header header = new BasicHeader("name", "value"); final Header header2 = new BasicHeader("name", "value"); headergroup.addHeader(header); Assertions.assertEquals(1, headergroup.getHeaders().length); Assertions.assertTrue(headergroup.removeHeader(header2)); Assertions.assertEquals(0, headergroup.getHeaders().length); } @Test void testUpdateHeader() { final HeaderGroup headergroup = new HeaderGroup(); final Header header1 = new BasicHeader("name1", "value1"); final Header header2 = new BasicHeader("name2", "value2"); final Header header3 = new BasicHeader("name3", "value3"); headergroup.addHeader(header1); headergroup.addHeader(header2); headergroup.addHeader(header3); headergroup.setHeader(new BasicHeader("name2", "newvalue")); headergroup.setHeader(new BasicHeader("name4", "value4")); headergroup.setHeader(null); Assertions.assertEquals(4, headergroup.getHeaders().length); Assertions.assertEquals("newvalue", headergroup.getFirstHeader("name2").getValue()); } @Test void testSetHeaders() { final HeaderGroup headergroup = new HeaderGroup(); final Header header1 = new BasicHeader("name1", "value1"); final Header header2 = new BasicHeader("name2", "value2"); final Header header3 = new BasicHeader("name3", "value3"); headergroup.addHeader(header1); headergroup.setHeaders(header2, header3); Assertions.assertEquals(2, headergroup.getHeaders().length); Assertions.assertEquals(0, headergroup.getHeaders("name1").length); Assertions.assertFalse(headergroup.containsHeader("name1")); Assertions.assertEquals(1, headergroup.getHeaders("name2").length); Assertions.assertTrue(headergroup.containsHeader("name2")); Assertions.assertEquals(1, headergroup.getHeaders("name3").length); Assertions.assertTrue(headergroup.containsHeader("name3")); headergroup.setHeaders(); Assertions.assertEquals(0, headergroup.getHeaders().length); } @Test void testFirstLastHeaders() { final HeaderGroup headergroup = new HeaderGroup(); final Header header1 = new BasicHeader("name", "value1"); final Header header2 = new BasicHeader("name", "value2"); final Header header3 = new BasicHeader("name", "value3"); headergroup.setHeaders(header1, header2, header3); Assertions.assertNull(headergroup.getFirstHeader("whatever")); Assertions.assertNull(headergroup.getLastHeader("whatever")); Assertions.assertEquals("value1", headergroup.getFirstHeader("name").getValue()); Assertions.assertEquals("value3", headergroup.getLastHeader("name").getValue()); } @Test void testCondensedHeader() { final HeaderGroup headergroup = new HeaderGroup(); Assertions.assertNull(headergroup.getCondensedHeader("name")); final Header header1 = new BasicHeader("name", "value1"); final Header header2 = new BasicHeader("name", "value2"); final Header header3 = new BasicHeader("name", "value3"); headergroup.setHeaders(header1, header2, header3); Assertions.assertEquals("value1, value2, value3", headergroup.getCondensedHeader("name").getValue()); headergroup.setHeaders(header1); Assertions.assertEquals(header1, headergroup.getCondensedHeader("name")); } @Test void testIterator() { final HeaderGroup headergroup = new HeaderGroup(); final Iterator
i = headergroup.headerIterator(); Assertions.assertNotNull(i); Assertions.assertFalse(i.hasNext()); } @Test void testHeaderRemove() { final HeaderGroup headergroup = new HeaderGroup(); final Header header1 = new BasicHeader("name", "value1"); final Header header2 = new BasicHeader("name", "value2"); final Header header3 = new BasicHeader("name", "value3"); headergroup.setHeaders(header1, header2, header3); final Iterator
i = headergroup.headerIterator(); Assertions.assertNotNull(i); Assertions.assertTrue(i.hasNext()); i.next(); Assertions.assertTrue(i.hasNext()); i.next(); i.remove(); Assertions.assertEquals(2, headergroup.getHeaders().length); Assertions.assertTrue(i.hasNext()); i.next(); i.remove(); Assertions.assertEquals(1, headergroup.getHeaders().length); Assertions.assertFalse(i.hasNext()); } @Test void testSerialization() throws Exception { final HeaderGroup orig = new HeaderGroup(); final Header header1 = new BasicHeader("name", "value1"); final Header header2 = new BasicHeader("name", "value2"); final Header header3 = new BasicHeader("name", "value3"); orig.setHeaders(header1, header2, header3); final ByteArrayOutputStream outbuffer = new ByteArrayOutputStream(); try (ObjectOutputStream outStream = new ObjectOutputStream(outbuffer)) { outStream.writeObject(orig); } final byte[] raw = outbuffer.toByteArray(); final ByteArrayInputStream inBuffer = new ByteArrayInputStream(raw); final ObjectInputStream inStream = new ObjectInputStream(inBuffer); final HeaderGroup clone = (HeaderGroup) inStream.readObject(); final Header[] headers1 = orig.getHeaders(); final Header[] headers2 = clone.getHeaders(); Assertions.assertNotNull(headers1); Assertions.assertNotNull(headers2); Assertions.assertEquals(headers1.length, headers2.length); for (int i = 0; i < headers1.length; i++) { Assertions.assertEquals(headers1[i].getName(), headers2[i].getName()); Assertions.assertEquals(headers1[i].getValue(), headers2[i].getValue()); } } } httpcore5/src/test/java/org/apache/hc/core5/http/message/TestMessageSupport.java0100644 0000000 0000000 00000046074 00000250600 026710 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Set; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HeaderElement; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpMessage; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.io.entity.HttpEntities; import org.apache.hc.core5.http.support.BasicResponseBuilder; import org.apache.hc.core5.util.CharArrayBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestMessageSupport { private static Set makeSet(final String... tokens) { if (tokens == null) { return null; } final Set set = new LinkedHashSet<>(); Collections.addAll(set, tokens); return set; } @Test void testTokenSetFormatting() { final Header header = MessageSupport.header(HttpHeaders.TRAILER, makeSet("z", "b", "a")); Assertions.assertNotNull(header); Assertions.assertEquals("z, b, a", header.getValue()); } @Test void testTokenListFormatting() { final Header header = MessageSupport.headerOfTokens(HttpHeaders.TRAILER, Arrays.asList("z", "b", "a", "a")); Assertions.assertNotNull(header); Assertions.assertEquals("z, b, a, a", header.getValue()); } @Test void testTokenListFormattingWithTransformation() { final Header header = MessageSupport.headerOfTokens(HttpHeaders.TRAILER, Arrays.asList("z", "b", "a", "a"), e -> e.toUpperCase(Locale.ROOT)); Assertions.assertNotNull(header); Assertions.assertEquals("Z, B, A, A", header.getValue()); } @Test void testTokenSetFormattingSameName() { final Header header = MessageSupport.header(HttpHeaders.TRAILER, makeSet("a", "a", "a")); Assertions.assertNotNull(header); Assertions.assertEquals("a", header.getValue()); } @Test void testTokenListFormattingSameName() { final Header header = MessageSupport.header(HttpHeaders.TRAILER, "a", "a", "a"); Assertions.assertNotNull(header); Assertions.assertEquals("a, a, a", header.getValue()); } @Test void testParseTokensWithConsumer() { final String s = "a, b, c, c"; final ParserCursor cursor = new ParserCursor(0, s.length()); final List tokens = new ArrayList<>(); MessageSupport.parseTokens(s, cursor, tokens::add); Assertions.assertEquals(Arrays.asList("a", "b", "c", "c"), tokens); } @Test void testParseTokenHeaderWithConsumer() { final Header header = new BasicHeader(HttpHeaders.TRAILER, "a, b, c, c"); final List tokens = new ArrayList<>(); MessageSupport.parseTokens(header, tokens::add); Assertions.assertEquals(Arrays.asList("a", "b", "c", "c"), tokens); } @Test void testParseTokenBufferWithConsumer() { final CharArrayBuffer buf = new CharArrayBuffer(128); buf.append("stuff: a, b, c, c"); final Header header = BufferedHeader.create(buf); Assertions.assertEquals(makeSet("a", "b", "c", "c"), MessageSupport.parseTokens(header)); } @Test void testParseTokens() { final String s = "a, b, c, c"; final ParserCursor cursor = new ParserCursor(0, s.length()); Assertions.assertEquals(makeSet("a", "b", "c"), MessageSupport.parseTokens(s, cursor)); } @Test void testParseTokenHeader() { final Header header = new BasicHeader(HttpHeaders.TRAILER, "a, b, c, c"); Assertions.assertEquals(makeSet("a", "b", "c"), MessageSupport.parseTokens(header)); } @Test void testParseTokenBuffer() { final CharArrayBuffer buf = new CharArrayBuffer(128); buf.append("stuff: a, b, c, c"); final Header header = BufferedHeader.create(buf); Assertions.assertEquals(makeSet("a", "b", "c"), MessageSupport.parseTokens(header)); } @Test void testElementListFormatting() { final List elements = Arrays.asList( new BasicHeaderElement("name1", "value1", new BasicNameValuePair("param", "regular_stuff")), new BasicHeaderElement("name2", "value2", new BasicNameValuePair("param", "this\\that")), new BasicHeaderElement("name3", "value3", new BasicNameValuePair("param", "this,that")), new BasicHeaderElement("name4", "value4", new BasicNameValuePair("param", null)), new BasicHeaderElement("name5", null)); final Header header = MessageSupport.headerOfElements("Some-header", elements); Assertions.assertNotNull(header); Assertions.assertEquals("name1=value1; param=regular_stuff, name2=value2; " + "param=\"this\\\\that\", name3=value3; param=\"this,that\", " + "name4=value4; param, name5", header.getValue()); } @Test void testElementArrayFormatting() { final HeaderElement[] elements = { new BasicHeaderElement("name1", "value1", new BasicNameValuePair("param", "regular_stuff")), new BasicHeaderElement("name2", "value2", new BasicNameValuePair("param", "this\\that")), new BasicHeaderElement("name3", "value3", new BasicNameValuePair("param", "this,that")), new BasicHeaderElement("name4", "value4", new BasicNameValuePair("param", null)), new BasicHeaderElement("name5", null)}; final Header header = MessageSupport.header("Some-Header", elements); Assertions.assertNotNull(header); Assertions.assertEquals("name1=value1; param=regular_stuff, name2=value2; " + "param=\"this\\\\that\", name3=value3; param=\"this,that\", " + "name4=value4; param, name5", header.getValue()); } @Test void testParseElementsBufferWithConsumer() { final CharArrayBuffer buf = new CharArrayBuffer(64); buf.append("name1 = value1; name2; name3=\"value3\" , name4=value4; " + "name5=value5, name6= ; name7 = value7; name8 = \" value8\""); final List elements = new ArrayList<>(); final ParserCursor cursor = new ParserCursor(0, buf.length()); MessageSupport.parseElements(buf, cursor, elements::add); // there are 3 elements Assertions.assertEquals(3,elements.size()); // 1st element Assertions.assertEquals("name1", elements.get(0).getName()); Assertions.assertEquals("value1", elements.get(0).getValue()); // 1st element has 2 getParameters() Assertions.assertEquals(2, elements.get(0).getParameters().length); Assertions.assertEquals("name2", elements.get(0).getParameters()[0].getName()); Assertions.assertNull(elements.get(0).getParameters()[0].getValue()); Assertions.assertEquals("name3", elements.get(0).getParameters()[1].getName()); Assertions.assertEquals("value3", elements.get(0).getParameters()[1].getValue()); // 2nd element Assertions.assertEquals("name4", elements.get(1).getName()); Assertions.assertEquals("value4", elements.get(1).getValue()); // 2nd element has 1 parameter Assertions.assertEquals(1, elements.get(1).getParameters().length); Assertions.assertEquals("name5", elements.get(1).getParameters()[0].getName()); Assertions.assertEquals("value5", elements.get(1).getParameters()[0].getValue()); // 3rd element Assertions.assertEquals("name6", elements.get(2).getName()); Assertions.assertEquals("", elements.get(2).getValue()); // 3rd element has 2 getParameters() Assertions.assertEquals(2, elements.get(2).getParameters().length); Assertions.assertEquals("name7", elements.get(2).getParameters()[0].getName()); Assertions.assertEquals("value7", elements.get(2).getParameters()[0].getValue()); Assertions.assertEquals("name8", elements.get(2).getParameters()[1].getName()); Assertions.assertEquals(" value8", elements.get(2).getParameters()[1].getValue()); } @Test void testParseElementsHeaderWithConsumer() { final Header header = new BasicHeader("Some-Header", "name1 = value1; name2; name3=\"value3\" , name4=value4; " + "name5=value5, name6= ; name7 = value7; name8 = \" value8\""); final List elements = new ArrayList<>(); MessageSupport.parseElements(header, elements::add); // there are 3 elements Assertions.assertEquals(3,elements.size()); // 1st element Assertions.assertEquals("name1", elements.get(0).getName()); Assertions.assertEquals("value1", elements.get(0).getValue()); // 1st element has 2 getParameters() Assertions.assertEquals(2, elements.get(0).getParameters().length); Assertions.assertEquals("name2", elements.get(0).getParameters()[0].getName()); Assertions.assertNull(elements.get(0).getParameters()[0].getValue()); Assertions.assertEquals("name3", elements.get(0).getParameters()[1].getName()); Assertions.assertEquals("value3", elements.get(0).getParameters()[1].getValue()); // 2nd element Assertions.assertEquals("name4", elements.get(1).getName()); Assertions.assertEquals("value4", elements.get(1).getValue()); // 2nd element has 1 parameter Assertions.assertEquals(1, elements.get(1).getParameters().length); Assertions.assertEquals("name5", elements.get(1).getParameters()[0].getName()); Assertions.assertEquals("value5", elements.get(1).getParameters()[0].getValue()); // 3rd element Assertions.assertEquals("name6", elements.get(2).getName()); Assertions.assertEquals("", elements.get(2).getValue()); // 3rd element has 2 getParameters() Assertions.assertEquals(2, elements.get(2).getParameters().length); Assertions.assertEquals("name7", elements.get(2).getParameters()[0].getName()); Assertions.assertEquals("value7", elements.get(2).getParameters()[0].getValue()); Assertions.assertEquals("name8", elements.get(2).getParameters()[1].getName()); Assertions.assertEquals(" value8", elements.get(2).getParameters()[1].getValue()); } @Test void testParseElementsHeader() { final Header header = new BasicHeader("Some-Header", "name1 = value1; name2; name3=\"value3\" , name4=value4; " + "name5=value5, name6= ; name7 = value7; name8 = \" value8\""); final List elements = MessageSupport.parseElements(header); // there are 3 elements Assertions.assertEquals(3,elements.size()); // 1st element Assertions.assertEquals("name1", elements.get(0).getName()); Assertions.assertEquals("value1", elements.get(0).getValue()); // 1st element has 2 getParameters() Assertions.assertEquals(2, elements.get(0).getParameters().length); Assertions.assertEquals("name2", elements.get(0).getParameters()[0].getName()); Assertions.assertNull(elements.get(0).getParameters()[0].getValue()); Assertions.assertEquals("name3", elements.get(0).getParameters()[1].getName()); Assertions.assertEquals("value3", elements.get(0).getParameters()[1].getValue()); // 2nd element Assertions.assertEquals("name4", elements.get(1).getName()); Assertions.assertEquals("value4", elements.get(1).getValue()); // 2nd element has 1 parameter Assertions.assertEquals(1, elements.get(1).getParameters().length); Assertions.assertEquals("name5", elements.get(1).getParameters()[0].getName()); Assertions.assertEquals("value5", elements.get(1).getParameters()[0].getValue()); // 3rd element Assertions.assertEquals("name6", elements.get(2).getName()); Assertions.assertEquals("", elements.get(2).getValue()); // 3rd element has 2 getParameters() Assertions.assertEquals(2, elements.get(2).getParameters().length); Assertions.assertEquals("name7", elements.get(2).getParameters()[0].getName()); Assertions.assertEquals("value7", elements.get(2).getParameters()[0].getValue()); Assertions.assertEquals("name8", elements.get(2).getParameters()[1].getName()); Assertions.assertEquals(" value8", elements.get(2).getParameters()[1].getValue()); } @Test void testParamListFormatting() { final CharArrayBuffer buf = new CharArrayBuffer(64); MessageSupport.formatParameters(buf, Arrays.asList( new BasicNameValuePair("param", "regular_stuff"), new BasicNameValuePair("param", "this\\that"), new BasicNameValuePair("param", "this,that") )); Assertions.assertEquals("param=regular_stuff; param=\"this\\\\that\"; param=\"this,that\"", buf.toString()); } @Test void testParamArrayFormatting() { final CharArrayBuffer buf = new CharArrayBuffer(64); MessageSupport.formatParameters(buf, new BasicNameValuePair("param", "regular_stuff"), new BasicNameValuePair("param", "this\\that"), new BasicNameValuePair("param", "this,that") ); Assertions.assertEquals("param=regular_stuff; param=\"this\\\\that\"; param=\"this,that\"", buf.toString()); } @Test void testParseParams() { final String s = "test; test1 = stuff ; test2 = \"stuff; stuff\"; test3=stuff,123"; final CharArrayBuffer buffer = new CharArrayBuffer(16); buffer.append(s); final ParserCursor cursor = new ParserCursor(0, s.length()); final List params = new ArrayList<>(); MessageSupport.parseParameters(buffer, cursor, params::add); Assertions.assertEquals("test", params.get(0).getName()); Assertions.assertNull(params.get(0).getValue()); Assertions.assertEquals("test1", params.get(1).getName()); Assertions.assertEquals("stuff", params.get(1).getValue()); Assertions.assertEquals("test2", params.get(2).getName()); Assertions.assertEquals("stuff; stuff", params.get(2).getValue()); Assertions.assertEquals("test3", params.get(3).getName()); Assertions.assertEquals("stuff", params.get(3).getValue()); Assertions.assertEquals(s.length() - 4, cursor.getPos()); Assertions.assertFalse(cursor.atEnd()); } @Test void testAddContentHeaders() { final HttpEntity entity = HttpEntities.create("some stuff with trailers", StandardCharsets.US_ASCII, new BasicHeader("z", "this"), new BasicHeader("b", "that"), new BasicHeader("a", "this and that")); final HttpMessage message = new BasicHttpResponse(200); MessageSupport.addTrailerHeader(message, entity); MessageSupport.addContentTypeHeader(message, entity); final Header h1 = message.getFirstHeader(HttpHeaders.TRAILER); final Header h2 = message.getFirstHeader(HttpHeaders.CONTENT_TYPE); Assertions.assertNotNull(h1); Assertions.assertEquals("z, b, a", h1.getValue()); Assertions.assertNotNull(h2); Assertions.assertEquals("text/plain; charset=US-ASCII", h2.getValue()); } @Test void testContentHeadersAlreadyPresent() { final HttpEntity entity = HttpEntities.create("some stuff with trailers", StandardCharsets.US_ASCII, new BasicHeader("z", "this"), new BasicHeader("b", "that"), new BasicHeader("a", "this and that")); final HttpMessage message = new BasicHttpResponse(200); message.addHeader(HttpHeaders.TRAILER, "a, a, a"); message.addHeader(HttpHeaders.CONTENT_TYPE, "text/plain; charset=ascii"); MessageSupport.addTrailerHeader(message, entity); MessageSupport.addContentTypeHeader(message, entity); final Header h1 = message.getFirstHeader(HttpHeaders.TRAILER); final Header h2 = message.getFirstHeader(HttpHeaders.CONTENT_TYPE); Assertions.assertNotNull(h1); Assertions.assertEquals("a, a, a", h1.getValue()); Assertions.assertNotNull(h2); Assertions.assertEquals("text/plain; charset=ascii", h2.getValue()); } @Test void testHopByHopHeaders() { Assertions.assertTrue(MessageSupport.isHopByHop("Connection")); Assertions.assertTrue(MessageSupport.isHopByHop("connection")); Assertions.assertTrue(MessageSupport.isHopByHop("coNNection")); Assertions.assertFalse(MessageSupport.isHopByHop("Content-Type")); Assertions.assertFalse(MessageSupport.isHopByHop("huh")); } @Test void testHopByHopHeadersConnectionSpecific() { final HttpResponse response = BasicResponseBuilder.create(HttpStatus.SC_OK) .addHeader(HttpHeaders.CONNECTION, "blah, blah, this, that") .addHeader(HttpHeaders.CONTENT_TYPE, ContentType.TEXT_PLAIN.toString()) .build(); final Set hopByHopConnectionSpecific = MessageSupport.hopByHopConnectionSpecific(response); Assertions.assertTrue(hopByHopConnectionSpecific.contains("Connection")); Assertions.assertTrue(hopByHopConnectionSpecific.contains("connection")); Assertions.assertTrue(hopByHopConnectionSpecific.contains("coNNection")); Assertions.assertFalse(hopByHopConnectionSpecific.contains("Content-Type")); Assertions.assertTrue(hopByHopConnectionSpecific.contains("blah")); Assertions.assertTrue(hopByHopConnectionSpecific.contains("Blah")); Assertions.assertTrue(hopByHopConnectionSpecific.contains("This")); Assertions.assertTrue(hopByHopConnectionSpecific.contains("That")); } } httpcore5/src/test/java/org/apache/hc/core5/http/message/TestNameValuePair.java0100644 0000000 0000000 00000015665 00000250600 026422 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.message; import org.apache.hc.core5.http.NameValuePair; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link NameValuePair}. * */ class TestNameValuePair { @Test void testConstructor() { final NameValuePair param = new BasicNameValuePair("name", "value"); Assertions.assertEquals("name", param.getName()); Assertions.assertEquals("value", param.getValue()); } @Test void testInvalidName() { Assertions.assertThrows(NullPointerException.class, () -> new BasicNameValuePair(null, null)); } @Test void testToString() { final NameValuePair param1 = new BasicNameValuePair("name1", "value1"); Assertions.assertEquals("name1=value1", param1.toString()); final NameValuePair param2 = new BasicNameValuePair("name1", null); Assertions.assertEquals("name1", param2.toString()); } @Test void testNullNotEqual() { final NameValuePair NameValuePair = new BasicNameValuePair("name", "value"); Assertions.assertNotEquals(null, NameValuePair); } @Test void testObjectNotEqual() { final NameValuePair NameValuePair = new BasicNameValuePair("name", "value"); Assertions.assertNotEquals(NameValuePair, new Object()); } @Test void testNameEquals() { final NameValuePair NameValuePair = new BasicNameValuePair("name", "value"); final NameValuePair NameValuePair2 = new BasicNameValuePair("NAME", "value"); Assertions.assertEquals(NameValuePair, NameValuePair2); Assertions.assertEquals(NameValuePair.hashCode(), NameValuePair2.hashCode()); final NameValuePair NameValuePair3 = new BasicNameValuePair("NAME", "value"); final NameValuePair NameValuePair4 = new BasicNameValuePair("name", "value"); Assertions.assertEquals(NameValuePair3, NameValuePair4); Assertions.assertEquals(NameValuePair3.hashCode(), NameValuePair4.hashCode()); } @Test void testValueEquals() { final NameValuePair NameValuePair = new BasicNameValuePair("name", "value"); final NameValuePair NameValuePair2 = new BasicNameValuePair("name", "value"); Assertions.assertEquals(NameValuePair, NameValuePair2); Assertions.assertEquals(NameValuePair.hashCode(), NameValuePair2.hashCode()); } @Test void testNameNotEqual() { final NameValuePair NameValuePair = new BasicNameValuePair("name", "value"); final NameValuePair NameValuePair2 = new BasicNameValuePair("name2", "value"); Assertions.assertNotEquals(NameValuePair, NameValuePair2); } @Test void testValueNotEqual() { final NameValuePair NameValuePair = new BasicNameValuePair("name", "value"); final NameValuePair NameValuePair2 = new BasicNameValuePair("name", "value2"); Assertions.assertNotEquals(NameValuePair, NameValuePair2); final NameValuePair NameValuePair3 = new BasicNameValuePair("name", "value"); final NameValuePair NameValuePair4 = new BasicNameValuePair("name", "VALUE"); Assertions.assertNotEquals(NameValuePair3, NameValuePair4); final NameValuePair NameValuePair5 = new BasicNameValuePair("name", "VALUE"); final NameValuePair NameValuePair6 = new BasicNameValuePair("name", "value"); Assertions.assertNotEquals(NameValuePair5, NameValuePair6); } @Test void testNullValuesEquals() { final NameValuePair NameValuePair = new BasicNameValuePair("name", null); final NameValuePair NameValuePair2 = new BasicNameValuePair("name", null); Assertions.assertEquals(NameValuePair, NameValuePair2); Assertions.assertEquals(NameValuePair.hashCode(), NameValuePair2.hashCode()); } @Test void testNullValueNotEqual() { final NameValuePair NameValuePair = new BasicNameValuePair("name", null); final NameValuePair NameValuePair2 = new BasicNameValuePair("name", "value"); Assertions.assertNotEquals(NameValuePair, NameValuePair2); } @Test void testNullValue2NotEqual() { final NameValuePair NameValuePair = new BasicNameValuePair("name", "value"); final NameValuePair NameValuePair2 = new BasicNameValuePair("name", null); Assertions.assertNotEquals(NameValuePair, NameValuePair2); } @Test void testEquals() { final NameValuePair NameValuePair = new BasicNameValuePair("name", "value"); final NameValuePair NameValuePair2 = new BasicNameValuePair("name", "value"); Assertions.assertEquals(NameValuePair, NameValuePair); Assertions.assertEquals(NameValuePair.hashCode(), NameValuePair.hashCode()); Assertions.assertEquals(NameValuePair2, NameValuePair2); Assertions.assertEquals(NameValuePair2.hashCode(), NameValuePair2.hashCode()); Assertions.assertEquals(NameValuePair, NameValuePair2); Assertions.assertEquals(NameValuePair.hashCode(), NameValuePair2.hashCode()); } @Test void testHashCode() { final NameValuePair NameValuePair = new BasicNameValuePair("name", null); final NameValuePair NameValuePair2 = new BasicNameValuePair("name2", null); Assertions.assertNotEquals(NameValuePair.hashCode(), NameValuePair2.hashCode()); final NameValuePair NameValuePair3 = new BasicNameValuePair("name", "value"); final NameValuePair NameValuePair4 = new BasicNameValuePair("name", "value2"); Assertions.assertNotEquals(NameValuePair3.hashCode(), NameValuePair4.hashCode()); final NameValuePair NameValuePair5 = new BasicNameValuePair("name", "value"); final NameValuePair NameValuePair6 = new BasicNameValuePair("name", null); Assertions.assertNotEquals(NameValuePair5.hashCode(), NameValuePair6.hashCode()); } } httpcore5/src/test/java/org/apache/hc/core5/http/nio/0040755 0000000 0000000 00000000000 00000250600 021355 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/nio/BasicDataStreamChannel.java0100644 0000000 0000000 00000004467 00000250600 026510 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.util.ArrayList; import java.util.List; import org.apache.hc.core5.http.Header; public class BasicDataStreamChannel implements DataStreamChannel { private final WritableByteChannel byteChannel; private List
trailers; public BasicDataStreamChannel(final WritableByteChannel byteChannel) { this.byteChannel = byteChannel; } @Override public void requestOutput() { } @Override public int write(final ByteBuffer src) throws IOException { return byteChannel.write(src); } @Override public void endStream() throws IOException { if (byteChannel.isOpen()) { byteChannel.close(); } } @Override public void endStream(final List trailers) throws IOException { endStream(); if (trailers != null) { this.trailers = new ArrayList<>(); this.trailers.addAll(trailers); } } public List
getTrailers() { return trailers; } } httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/0040755 0000000 0000000 00000000000 00000250600 022671 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractBinAsyncEntityConsumer.java0100644 0000000 0000000 00000006552 00000250600 032504 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.nio.ByteBuffer; import org.apache.hc.core5.concurrent.CountingFutureCallback; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.impl.BasicEntityDetails; import org.apache.hc.core5.http.nio.AsyncEntityConsumer; import org.apache.hc.core5.util.ByteArrayBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestAbstractBinAsyncEntityConsumer { static private class ByteArrayAsyncEntityConsumer extends AbstractBinAsyncEntityConsumer { private final ByteArrayBuffer buffer; public ByteArrayAsyncEntityConsumer() { super(); this.buffer = new ByteArrayBuffer(1024); } @Override protected void streamStart(final ContentType contentType) throws HttpException, IOException { } @Override protected int capacityIncrement() { return Integer.MAX_VALUE; } @Override protected void data(final ByteBuffer src, final boolean endOfStream) throws IOException { buffer.append(src); } @Override protected byte[] generateContent() throws IOException { return buffer.toByteArray(); } @Override public void releaseResources() { } } @Test void testConsumeData() throws Exception { final AsyncEntityConsumer consumer = new ByteArrayAsyncEntityConsumer(); final CountingFutureCallback countingCallback = new CountingFutureCallback<>(); consumer.streamStart(new BasicEntityDetails(-1, ContentType.APPLICATION_OCTET_STREAM), countingCallback); consumer.consume(ByteBuffer.wrap(new byte[]{'1', '2', '3'})); consumer.consume(ByteBuffer.wrap(new byte[]{'4', '5'})); consumer.consume(ByteBuffer.wrap(new byte[]{})); Assertions.assertNull(consumer.getContent()); consumer.streamEnd(null); Assertions.assertArrayEquals(new byte[] {'1', '2', '3', '4', '5'}, consumer.getContent()); Assertions.assertEquals(1L, countingCallback.getCount()); } } httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractBinAsyncEntityProducer.java0100644 0000000 0000000 00000015666 00000250600 032502 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.WritableByteChannelMock; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.BasicDataStreamChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.StreamChannel; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestAbstractBinAsyncEntityProducer { static private class ChunkByteAsyncEntityProducer extends AbstractBinAsyncEntityProducer { private final byte[][] content; private int count = 0; public ChunkByteAsyncEntityProducer( final int fragmentSizeHint, final ContentType contentType, final byte[]... content) { super(fragmentSizeHint, contentType); this.content = content; } @Override public boolean isRepeatable() { return false; } @Override protected int availableData() { return Integer.MAX_VALUE; } @Override protected void produceData(final StreamChannel channel) throws IOException { if (count < content.length) { channel.write(ByteBuffer.wrap(content[count])); } count++; if (count >= content.length) { channel.endStream(); } } @Override public void failed(final Exception cause) { } } @Test void testProduceDataNoBuffering() throws Exception { final AsyncEntityProducer producer = new ChunkByteAsyncEntityProducer( 0, ContentType.TEXT_PLAIN, new byte[] { '1', '2', '3' }, new byte[] { '4', '5', '6' }); Assertions.assertEquals(-1, producer.getContentLength()); Assertions.assertEquals(ContentType.TEXT_PLAIN.toString(), producer.getContentType()); Assertions.assertNull(producer.getContentEncoding()); final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel); producer.produce(streamChannel); Assertions.assertTrue(byteChannel.isOpen()); Assertions.assertEquals("123", byteChannel.dump(StandardCharsets.US_ASCII)); producer.produce(streamChannel); Assertions.assertFalse(byteChannel.isOpen()); Assertions.assertEquals("456", byteChannel.dump(StandardCharsets.US_ASCII)); } @Test void testProduceDataWithBuffering1() throws Exception { final AsyncEntityProducer producer = new ChunkByteAsyncEntityProducer( 5, ContentType.TEXT_PLAIN, new byte[] { '1', '2', '3' }, new byte[] { '4', '5', '6' }, new byte[] { '7', '8' }, new byte[] { '9', '0' }); final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel); producer.produce(streamChannel); Assertions.assertTrue(byteChannel.isOpen()); Assertions.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII)); producer.produce(streamChannel); Assertions.assertTrue(byteChannel.isOpen()); Assertions.assertEquals("123", byteChannel.dump(StandardCharsets.US_ASCII)); producer.produce(streamChannel); Assertions.assertTrue(byteChannel.isOpen()); Assertions.assertEquals("45678", byteChannel.dump(StandardCharsets.US_ASCII)); producer.produce(streamChannel); Assertions.assertFalse(byteChannel.isOpen()); Assertions.assertEquals("90", byteChannel.dump(StandardCharsets.US_ASCII)); } @Test void testProduceDataWithBuffering2() throws Exception { final AsyncEntityProducer producer = new ChunkByteAsyncEntityProducer( 5, ContentType.TEXT_PLAIN, new byte[] { '1' }, new byte[] { '2' }, new byte[] { '3' }, new byte[] { '4', '5' }, new byte[] { '6' }, new byte[] { '7', '8' }, new byte[] { '9', '0' }); final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel); producer.produce(streamChannel); Assertions.assertTrue(byteChannel.isOpen()); Assertions.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII)); producer.produce(streamChannel); Assertions.assertTrue(byteChannel.isOpen()); Assertions.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII)); producer.produce(streamChannel); Assertions.assertTrue(byteChannel.isOpen()); Assertions.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII)); producer.produce(streamChannel); Assertions.assertTrue(byteChannel.isOpen()); Assertions.assertEquals("12345", byteChannel.dump(StandardCharsets.US_ASCII)); producer.produce(streamChannel); Assertions.assertTrue(byteChannel.isOpen()); Assertions.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII)); producer.produce(streamChannel); Assertions.assertTrue(byteChannel.isOpen()); Assertions.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII)); producer.produce(streamChannel); Assertions.assertFalse(byteChannel.isOpen()); Assertions.assertEquals("67890", byteChannel.dump(StandardCharsets.US_ASCII)); } } ././@LongLink0100644 0000000 0000000 00000000145 00000250600 011611 Lustar 0000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractCharAsyncEntityConsumer.javahttpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractCharAsyncEntityConsumer.java0100644 0000000 0000000 00000011433 00000250600 032643 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.concurrent.CountingFutureCallback; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.impl.BasicEntityDetails; import org.apache.hc.core5.http.nio.AsyncEntityConsumer; import org.apache.hc.core5.util.ByteArrayBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestAbstractCharAsyncEntityConsumer { static private class StringBuilderAsyncEntityConsumer extends AbstractCharAsyncEntityConsumer { private final StringBuilder buffer; public StringBuilderAsyncEntityConsumer() { super(); this.buffer = new StringBuilder(1024); } @Override protected void streamStart(final ContentType contentType) throws HttpException, IOException { } @Override protected int capacityIncrement() { return Integer.MAX_VALUE; } @Override protected void data(final CharBuffer src, final boolean endOfStream) throws IOException { buffer.append(src); } @Override protected String generateContent() throws IOException { return buffer.toString(); } @Override public void releaseResources() { buffer.setLength(0); } } @Test void testConsumeData() throws Exception { final AsyncEntityConsumer consumer = new StringBuilderAsyncEntityConsumer(); final CountingFutureCallback countingCallback = new CountingFutureCallback<>(); consumer.streamStart(new BasicEntityDetails(-1, ContentType.TEXT_PLAIN), countingCallback); consumer.consume(ByteBuffer.wrap(new byte[]{'1', '2', '3'})); consumer.consume(ByteBuffer.wrap(new byte[]{'4', '5'})); consumer.consume(ByteBuffer.wrap(new byte[]{})); Assertions.assertNull(consumer.getContent()); consumer.streamEnd(null); Assertions.assertEquals("12345", consumer.getContent()); Assertions.assertEquals(1L, countingCallback.getCount()); } @Test void testConsumeIncompleteData() throws Exception { final AsyncEntityConsumer consumer = new StringBuilderAsyncEntityConsumer(); final CountingFutureCallback countingCallback = new CountingFutureCallback<>(); consumer.streamStart(new BasicEntityDetails(-1, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)), countingCallback); final byte[] stuff = "stuff".getBytes(StandardCharsets.UTF_8); final byte[] splitCharacter = "£".getBytes(StandardCharsets.UTF_8); final ByteArrayBuffer b1 = new ByteArrayBuffer(1024); b1.append(stuff, 0, stuff.length); b1.append(splitCharacter, 0, 1); consumer.consume(ByteBuffer.wrap(b1.toByteArray())); final ByteArrayBuffer b2 = new ByteArrayBuffer(1024); b2.append(splitCharacter, 1, 1); b2.append(stuff, 0, stuff.length); b2.append(splitCharacter, 0, 1); consumer.consume(ByteBuffer.wrap(b2.toByteArray())); final ByteArrayBuffer b3 = new ByteArrayBuffer(1024); b3.append(splitCharacter, 1, 1); b3.append(stuff, 0, stuff.length); consumer.consume(ByteBuffer.wrap(b3.toByteArray())); consumer.streamEnd(null); Assertions.assertEquals("stuff£stuff£stuff", consumer.getContent()); Assertions.assertEquals(1L, countingCallback.getCount()); } } ././@LongLink0100644 0000000 0000000 00000000145 00000250600 011611 Lustar 0000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractCharAsyncEntityProducer.javahttpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractCharAsyncEntityProducer.java0100644 0000000 0000000 00000011751 00000250600 032636 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.nio.CharBuffer; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.WritableByteChannelMock; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.BasicDataStreamChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.StreamChannel; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestAbstractCharAsyncEntityProducer { static private class ChunkCharAsyncEntityProducer extends AbstractCharAsyncEntityProducer { private final String[] content; private int count = 0; public ChunkCharAsyncEntityProducer( final int bufferSize, final int fragmentSizeHint, final ContentType contentType, final String... content) { super(bufferSize, fragmentSizeHint, contentType); this.content = content; } @Override public boolean isRepeatable() { return false; } @Override protected int availableData() { return Integer.MAX_VALUE; } @Override protected void produceData(final StreamChannel channel) throws IOException { if (count < content.length) { channel.write(CharBuffer.wrap(content[count])); } count++; if (count >= content.length) { channel.endStream(); } } @Override public void failed(final Exception cause) { } } @Test void testProduceDataNoBuffering() throws Exception { final AsyncEntityProducer producer = new ChunkCharAsyncEntityProducer( 256, 0, ContentType.TEXT_PLAIN, "this", "this and that"); Assertions.assertEquals(-1, producer.getContentLength()); Assertions.assertEquals(ContentType.TEXT_PLAIN.toString(), producer.getContentType()); Assertions.assertNull(producer.getContentEncoding()); final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel); producer.produce(streamChannel); Assertions.assertTrue(byteChannel.isOpen()); Assertions.assertEquals("this", byteChannel.dump(StandardCharsets.US_ASCII)); producer.produce(streamChannel); Assertions.assertFalse(byteChannel.isOpen()); Assertions.assertEquals("this and that", byteChannel.dump(StandardCharsets.US_ASCII)); } @Test void testProduceDataWithBuffering() throws Exception { final AsyncEntityProducer producer = new ChunkCharAsyncEntityProducer( 256, 5, ContentType.TEXT_PLAIN, "this", " and that", "all", " sorts of stuff"); final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel); producer.produce(streamChannel); Assertions.assertTrue(byteChannel.isOpen()); Assertions.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII)); producer.produce(streamChannel); Assertions.assertTrue(byteChannel.isOpen()); Assertions.assertEquals("this and that", byteChannel.dump(StandardCharsets.US_ASCII)); producer.produce(streamChannel); Assertions.assertTrue(byteChannel.isOpen()); Assertions.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII)); producer.produce(streamChannel); Assertions.assertFalse(byteChannel.isOpen()); Assertions.assertEquals("all sorts of stuff", byteChannel.dump(StandardCharsets.US_ASCII)); } } httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAsyncEntityProducers.java0100644 0000000 0000000 00000006214 00000250600 030535 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Tests {@link AsyncEntityProducers}. */ class TestAsyncEntityProducers { @Test void testPathEntityProducer() throws IOException { final Path path = Paths.get("src/test/resources/test-ssl.txt"); final AsyncEntityProducer producer = AsyncEntityProducers.create(path, ContentType.APPLICATION_OCTET_STREAM, StandardOpenOption.READ, LinkOption.NOFOLLOW_LINKS); try { Assertions.assertFalse(producer.isChunked()); Assertions.assertEquals(Files.size(path), producer.getContentLength()); Assertions.assertEquals(ContentType.APPLICATION_OCTET_STREAM.toString(), producer.getContentType()); } finally { producer.releaseResources(); } } @Test void testPathEntityProducerWithTrailers() throws IOException { final Path path = Paths.get("src/test/resources/test-ssl.txt"); final Header header1 = new BasicHeader("Tailer1", "Value1"); final Header header2 = new BasicHeader("Tailer2", "Value2"); final AsyncEntityProducer producer = AsyncEntityProducers.create(path, ContentType.APPLICATION_OCTET_STREAM, header1, header2); try { Assertions.assertTrue(producer.isChunked()); Assertions.assertEquals(-1, producer.getContentLength()); Assertions.assertEquals(ContentType.APPLICATION_OCTET_STREAM.toString(), producer.getContentType()); } finally { producer.releaseResources(); } } } httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestBasicAsyncEntityProducer.java0100644 0000000 0000000 00000011330 00000250600 031307 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.WritableByteChannelMock; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.BasicDataStreamChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestBasicAsyncEntityProducer { @Test void testBinaryContent() throws Exception { final AsyncEntityProducer producer = new BasicAsyncEntityProducer( new byte[] { 'a', 'b', 'c' }, ContentType.DEFAULT_BINARY); Assertions.assertEquals(3, producer.getContentLength()); Assertions.assertEquals(ContentType.DEFAULT_BINARY.toString(), producer.getContentType()); Assertions.assertNull(producer.getContentEncoding()); final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel); producer.produce(streamChannel); Assertions.assertFalse(byteChannel.isOpen()); Assertions.assertEquals("abc", byteChannel.dump(StandardCharsets.US_ASCII)); } @Test void testTextContent() throws Exception { final AsyncEntityProducer producer = new BasicAsyncEntityProducer( "abc", ContentType.TEXT_PLAIN); Assertions.assertEquals(3, producer.getContentLength()); Assertions.assertEquals(ContentType.TEXT_PLAIN.toString(), producer.getContentType()); Assertions.assertNull(producer.getContentEncoding()); final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel); producer.produce(streamChannel); Assertions.assertFalse(byteChannel.isOpen()); Assertions.assertEquals("abc", byteChannel.dump(StandardCharsets.US_ASCII)); } @Test void testTextContentRepeatable() throws Exception { final AsyncEntityProducer producer = new BasicAsyncEntityProducer( "abc", ContentType.TEXT_PLAIN); Assertions.assertEquals(3, producer.getContentLength()); Assertions.assertEquals(ContentType.TEXT_PLAIN.toString(), producer.getContentType()); Assertions.assertNull(producer.getContentEncoding()); for (int i = 0; i < 3; i++) { final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel); producer.produce(streamChannel); Assertions.assertFalse(byteChannel.isOpen()); Assertions.assertEquals("abc", byteChannel.dump(StandardCharsets.US_ASCII)); producer.releaseResources(); } } @Test void testTextContentRepeatableUTF() throws Exception { final String content = ""; final AsyncEntityProducer producer = new BasicAsyncEntityProducer(content, ContentType.TEXT_XML); for (int i = 0; i < 3; i++) { final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel); producer.produce(streamChannel); Assertions.assertFalse(byteChannel.isOpen()); Assertions.assertEquals(content, byteChannel.dump(StandardCharsets.UTF_8)); producer.releaseResources(); } } } httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestDigestingEntityConsumer.java0100644 0000000 0000000 00000003762 00000250600 031227 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.nio.ByteBuffer; import org.apache.hc.core5.util.TextUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestDigestingEntityConsumer { @Test void testConsumeData() throws Exception { final DigestingEntityConsumer consumer = new DigestingEntityConsumer<>("MD5", new StringAsyncEntityConsumer()); consumer.consume(ByteBuffer.wrap(new byte[]{'1', '2', '3'})); consumer.consume(ByteBuffer.wrap(new byte[]{'4', '5'})); consumer.consume(ByteBuffer.wrap(new byte[]{})); consumer.streamEnd(null); Assertions.assertEquals("12345", consumer.getContent()); Assertions.assertEquals("827ccb0eea8a706c4c34a16891f84e7b", TextUtils.toHexString(consumer.getDigest())); } } httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestDigestingEntityProducer.java0100644 0000000 0000000 00000005207 00000250600 031213 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.nio.charset.StandardCharsets; import java.util.List; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.WritableByteChannelMock; import org.apache.hc.core5.http.nio.BasicDataStreamChannel; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestDigestingEntityProducer { @Test void testProduceData() throws Exception { final DigestingEntityProducer producer = new DigestingEntityProducer("MD5", new StringAsyncEntityProducer("12345", ContentType.TEXT_PLAIN)); final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final BasicDataStreamChannel dataStreamChannel = new BasicDataStreamChannel(byteChannel); while (byteChannel.isOpen()) { producer.produce(dataStreamChannel); } Assertions.assertEquals("12345", byteChannel.dump(StandardCharsets.US_ASCII)); final List
trailers = dataStreamChannel.getTrailers(); Assertions.assertNotNull(trailers); Assertions.assertEquals(2, trailers.size()); Assertions.assertEquals("digest-algo", trailers.get(0).getName()); Assertions.assertEquals("MD5", trailers.get(0).getValue()); Assertions.assertEquals("digest", trailers.get(1).getName()); Assertions.assertEquals("827ccb0eea8a706c4c34a16891f84e7b", trailers.get(1).getValue()); } } httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestFileAsyncEntityProducer.java0100644 0000000 0000000 00000007723 00000250600 031160 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.WritableByteChannelMock; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.BasicDataStreamChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class TestFileAsyncEntityProducer { private File tempFile; @BeforeEach void setup() throws Exception { tempFile = File.createTempFile("testing", ".txt"); try (final Writer writer = new OutputStreamWriter(new FileOutputStream(tempFile), StandardCharsets.US_ASCII)) { writer.append("abcdef"); writer.flush(); } } @AfterEach void cleanup() { if (tempFile != null) { tempFile.delete(); tempFile = null; } } @Test void testTextContent() throws Exception { final AsyncEntityProducer producer = new FileEntityProducer(tempFile, ContentType.TEXT_PLAIN); Assertions.assertEquals(6, producer.getContentLength()); Assertions.assertEquals(ContentType.TEXT_PLAIN.toString(), producer.getContentType()); Assertions.assertNull(producer.getContentEncoding()); final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel); producer.produce(streamChannel); producer.produce(streamChannel); Assertions.assertFalse(byteChannel.isOpen()); Assertions.assertEquals("abcdef", byteChannel.dump(StandardCharsets.US_ASCII)); } @Test void testTextContentRepeatable() throws Exception { final AsyncEntityProducer producer = new FileEntityProducer(tempFile, ContentType.TEXT_PLAIN); Assertions.assertEquals(6, producer.getContentLength()); Assertions.assertEquals(ContentType.TEXT_PLAIN.toString(), producer.getContentType()); Assertions.assertNull(producer.getContentEncoding()); for (int i = 0; i < 3; i++) { final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel); producer.produce(streamChannel); producer.produce(streamChannel); Assertions.assertFalse(byteChannel.isOpen()); Assertions.assertEquals("abcdef", byteChannel.dump(StandardCharsets.US_ASCII)); producer.releaseResources(); } } } httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestFileEntityProducer.java0100644 0000000 0000000 00000004061 00000250600 030152 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.file.Files; import java.nio.file.Path; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; /** * @since 5.0 */ class TestFileEntityProducer { @Test void testFileLengthMaxIntPlusOne(@TempDir final Path tempFolder) throws IOException { final Path path = Files.createFile(tempFolder.resolve("test.bin")); try (RandomAccessFile raFile = new RandomAccessFile(path.toFile(), "rw")) { final long expectedLength = 1L + Integer.MAX_VALUE; raFile.setLength(expectedLength); final FileEntityProducer fileEntityProducer = new FileEntityProducer(path.toFile()); Assertions.assertEquals(expectedLength, fileEntityProducer.getContentLength()); } } } httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestPathAsyncEntityProducer.java0100644 0000000 0000000 00000010254 00000250600 031166 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.WritableByteChannelMock; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.BasicDataStreamChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class TestPathAsyncEntityProducer { private File tempFile; @AfterEach void cleanup() { if (tempFile != null) { tempFile.delete(); tempFile = null; } } @BeforeEach void setup() throws Exception { tempFile = File.createTempFile("testing", ".txt"); try (final Writer writer = new OutputStreamWriter(new FileOutputStream(tempFile), StandardCharsets.US_ASCII)) { writer.append("abcdef"); writer.flush(); } } @Test void testTextContent() throws Exception { final Path tempPath = tempFile.toPath(); final AsyncEntityProducer producer = new PathEntityProducer(tempPath, ContentType.TEXT_PLAIN, StandardOpenOption.READ); Assertions.assertEquals(6, producer.getContentLength()); Assertions.assertEquals(ContentType.TEXT_PLAIN.toString(), producer.getContentType()); Assertions.assertNull(producer.getContentEncoding()); final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel); producer.produce(streamChannel); producer.produce(streamChannel); Assertions.assertFalse(byteChannel.isOpen()); Assertions.assertEquals("abcdef", byteChannel.dump(StandardCharsets.US_ASCII)); } @Test void testTextContentRepeatable() throws Exception { final Path tempPath = tempFile.toPath(); final AsyncEntityProducer producer = new PathEntityProducer(tempPath, ContentType.TEXT_PLAIN, StandardOpenOption.READ); Assertions.assertEquals(6, producer.getContentLength()); Assertions.assertEquals(ContentType.TEXT_PLAIN.toString(), producer.getContentType()); Assertions.assertNull(producer.getContentEncoding()); for (int i = 0; i < 3; i++) { final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel); producer.produce(streamChannel); producer.produce(streamChannel); Assertions.assertFalse(byteChannel.isOpen()); Assertions.assertEquals("abcdef", byteChannel.dump(StandardCharsets.US_ASCII)); producer.releaseResources(); } } } httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestPathEntityProducer.java0100644 0000000 0000000 00000004152 00000250600 030170 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; /** * @since 5.0 */ class TestPathEntityProducer { @Test void testFileLengthMaxIntPlusOne(@TempDir final Path tempFolder) throws IOException { final Path path = Files.createFile(tempFolder.resolve("test.bin")); try (RandomAccessFile raFile = new RandomAccessFile(path.toFile(), "rw")) { final long expectedLength = 1L + Integer.MAX_VALUE; raFile.setLength(expectedLength); final PathEntityProducer fileEntityProducer = new PathEntityProducer(path, StandardOpenOption.READ); Assertions.assertEquals(expectedLength, fileEntityProducer.getContentLength()); } } } httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestStringAsyncEntityProducer.java0100644 0000000 0000000 00000006377 00000250600 031553 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.entity; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.WritableByteChannelMock; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.BasicDataStreamChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestStringAsyncEntityProducer { @Test void testTextContent() throws Exception { final AsyncEntityProducer producer = new StringAsyncEntityProducer( "abc", ContentType.TEXT_PLAIN); Assertions.assertEquals(-1, producer.getContentLength()); Assertions.assertEquals(ContentType.TEXT_PLAIN.toString(), producer.getContentType()); Assertions.assertNull(producer.getContentEncoding()); final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel); producer.produce(streamChannel); Assertions.assertFalse(byteChannel.isOpen()); Assertions.assertEquals("abc", byteChannel.dump(StandardCharsets.US_ASCII)); } @Test void testTextContentRepeatable() throws Exception { final AsyncEntityProducer producer = new StringAsyncEntityProducer( "abc", ContentType.TEXT_PLAIN); Assertions.assertEquals(-1, producer.getContentLength()); Assertions.assertEquals(ContentType.TEXT_PLAIN.toString(), producer.getContentType()); Assertions.assertNull(producer.getContentEncoding()); for (int i = 0; i < 3; i++) { final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel); producer.produce(streamChannel); Assertions.assertFalse(byteChannel.isOpen()); Assertions.assertEquals("abc", byteChannel.dump(StandardCharsets.US_ASCII)); producer.releaseResources(); } } } httpcore5/src/test/java/org/apache/hc/core5/http/nio/ssl/0040755 0000000 0000000 00000000000 00000250600 022156 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/nio/ssl/BasicClientTlsStrategyTest.java0100644 0000000 0000000 00000003541 00000250600 030247 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.ssl; import static org.junit.jupiter.api.Assertions.assertNotNull; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; class BasicClientTlsStrategyTest { @Mock private SSLSessionVerifier sslSessionVerifier; @BeforeEach void prepareMocks() { MockitoAnnotations.openMocks(this); } @Test void test_valid_tls_strategy_creation_with_verifier() { final BasicClientTlsStrategy strategy = new BasicClientTlsStrategy(sslSessionVerifier); assertNotNull(strategy); } }httpcore5/src/test/java/org/apache/hc/core5/http/nio/ssl/BasicServerTlsStrategyTest.java0100644 0000000 0000000 00000003541 00000250600 030277 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.ssl; import static org.junit.jupiter.api.Assertions.assertNotNull; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; class BasicServerTlsStrategyTest { @Mock private SSLSessionVerifier sslSessionVerifier; @BeforeEach void prepareMocks() { MockitoAnnotations.openMocks(this); } @Test void test_valid_tls_strategy_creation_with_verifier() { final BasicServerTlsStrategy strategy = new BasicServerTlsStrategy(sslSessionVerifier); assertNotNull(strategy); } }httpcore5/src/test/java/org/apache/hc/core5/http/nio/support/0040755 0000000 0000000 00000000000 00000250600 023071 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/nio/support/classic/0040755 0000000 0000000 00000000000 00000250600 024512 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/nio/support/classic/TestSharedInputBuffer.java0100644 0000000 0000000 00000020613 00000250600 031574 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support.classic; import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; class TestSharedInputBuffer { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @Test void testBasis() throws Exception { final Charset charset = StandardCharsets.US_ASCII; final SharedInputBuffer inputBuffer = new SharedInputBuffer(10); inputBuffer.fill(charset.encode("1234567890")); Assertions.assertEquals(10, inputBuffer.length()); final CapacityChannel capacityChannel = Mockito.mock(CapacityChannel.class); inputBuffer.updateCapacity(capacityChannel); Mockito.verifyNoInteractions(capacityChannel); inputBuffer.fill(charset.encode("1234567890")); inputBuffer.fill(charset.encode("1234567890")); Assertions.assertEquals(30, inputBuffer.length()); Mockito.verifyNoInteractions(capacityChannel); final byte[] tmp = new byte[20]; final int bytesRead1 = inputBuffer.read(tmp, 0, tmp.length); Assertions.assertEquals(20, bytesRead1); Mockito.verifyNoInteractions(capacityChannel); inputBuffer.markEndStream(); Assertions.assertEquals('1', inputBuffer.read()); Assertions.assertEquals('2', inputBuffer.read()); final int bytesRead2 = inputBuffer.read(tmp, 0, tmp.length); Assertions.assertEquals(8, bytesRead2); Mockito.verifyNoInteractions(capacityChannel); Assertions.assertEquals(-1, inputBuffer.read(tmp, 0, tmp.length)); Assertions.assertEquals(-1, inputBuffer.read(tmp, 0, tmp.length)); Assertions.assertEquals(-1, inputBuffer.read()); Assertions.assertEquals(-1, inputBuffer.read()); } @Test void testMultithreadingRead() throws Exception { final SharedInputBuffer inputBuffer = new SharedInputBuffer(10); final CapacityChannel capacityChannel = Mockito.mock(CapacityChannel.class); inputBuffer.updateCapacity(capacityChannel); Mockito.verify(capacityChannel).update(10); Mockito.reset(capacityChannel); final ExecutorService executorService = Executors.newFixedThreadPool(2); final Future task1 = executorService.submit(() -> { final Charset charset = StandardCharsets.US_ASCII; inputBuffer.fill(charset.encode("1234567890")); return Boolean.TRUE; }); final Future task2 = executorService.submit(() -> { final byte[] tmp = new byte[20]; return inputBuffer.read(tmp, 0, tmp.length); }); Assertions.assertEquals(Boolean.TRUE, task1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); Assertions.assertEquals(Integer.valueOf(10), task2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); Mockito.verify(capacityChannel).update(10); } @Test void testMultithreadingSingleRead() throws Exception { final SharedInputBuffer inputBuffer = new SharedInputBuffer(10); final CapacityChannel capacityChannel = Mockito.mock(CapacityChannel.class); inputBuffer.updateCapacity(capacityChannel); Mockito.verify(capacityChannel).update(10); Mockito.reset(capacityChannel); final ExecutorService executorService = Executors.newFixedThreadPool(2); final Future task1 = executorService.submit(() -> { final Charset charset = StandardCharsets.US_ASCII; inputBuffer.fill(charset.encode("a")); return Boolean.TRUE; }); final Future task2 = executorService.submit((Callable) inputBuffer::read); Assertions.assertEquals(Boolean.TRUE, task1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); Assertions.assertEquals(Integer.valueOf('a'), task2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); Mockito.verify(capacityChannel).update(1); } @Test void testMultithreadingReadStream() throws Exception { final SharedInputBuffer inputBuffer = new SharedInputBuffer(10); final CapacityChannel capacityChannel = Mockito.mock(CapacityChannel.class); inputBuffer.updateCapacity(capacityChannel); Mockito.verify(capacityChannel).update(10); Mockito.reset(capacityChannel); final ExecutorService executorService = Executors.newFixedThreadPool(2); final Future task1 = executorService.submit(() -> { final Charset charset = StandardCharsets.US_ASCII; final Random rnd = new Random(System.currentTimeMillis()); for (int i = 0; i < 5; i++) { inputBuffer.fill(charset.encode("1234567890")); Thread.sleep(rnd.nextInt(250)); } inputBuffer.markEndStream(); return Boolean.TRUE; }); final Future task2 = executorService.submit(() -> { final Charset charset = StandardCharsets.US_ASCII; final StringBuilder buf = new StringBuilder(); final byte[] tmp = new byte[10]; int l; while ((l = inputBuffer.read(tmp, 0, tmp.length)) != -1) { buf.append(charset.decode(ByteBuffer.wrap(tmp, 0, l))); } return buf.toString(); }); Assertions.assertEquals(Boolean.TRUE, task1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); Assertions.assertEquals("12345678901234567890123456789012345678901234567890", task2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); Mockito.verify(capacityChannel, Mockito.atLeast(1)).update(ArgumentMatchers.anyInt()); } @Test void testMultithreadingReadStreamAbort() throws Exception { final SharedInputBuffer inputBuffer = new SharedInputBuffer(10); final CapacityChannel capacityChannel = Mockito.mock(CapacityChannel.class); inputBuffer.updateCapacity(capacityChannel); Mockito.verify(capacityChannel).update(10); Mockito.reset(capacityChannel); final ExecutorService executorService = Executors.newFixedThreadPool(2); final Future task1 = executorService.submit(() -> { Thread.sleep(1000); inputBuffer.abort(); return Boolean.TRUE; }); final Future task2 = executorService.submit((Callable) inputBuffer::read); Assertions.assertEquals(Boolean.TRUE, task1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); final ExecutionException ex = Assertions.assertThrows(ExecutionException.class, () -> task2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); Assertions.assertInstanceOf(InterruptedIOException.class, ex.getCause()); Mockito.verify(capacityChannel, Mockito.never()).update(10); } } httpcore5/src/test/java/org/apache/hc/core5/http/nio/support/classic/TestSharedOutputBuffer.java0100644 0000000 0000000 00000022323 00000250600 031775 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.nio.support.classic; import java.io.IOException; import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.WritableByteChannelMock; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import org.mockito.Mockito; class TestSharedOutputBuffer { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); static class DataStreamChannelMock implements DataStreamChannel { private final WritableByteChannelMock channel; private final ReentrantLock lock; DataStreamChannelMock(final WritableByteChannelMock channel) { this.channel = channel; this.lock = new ReentrantLock(); } @Override public int write(final ByteBuffer src) throws IOException { lock.lock(); try { return channel.write(src); } finally { lock.unlock(); } } @Override public void requestOutput() { } @Override public void endStream(final List trailers) throws IOException { lock.lock(); try { channel.close(); } finally { lock.unlock(); } } @Override public void endStream() throws IOException { endStream(null); } } @Test void testBasis() throws Exception { final Charset charset = StandardCharsets.US_ASCII; final SharedOutputBuffer outputBuffer = new SharedOutputBuffer(30); final WritableByteChannelMock channel = new WritableByteChannelMock(1024); final DataStreamChannel dataStreamChannel = Mockito.spy(new DataStreamChannelMock(channel)); outputBuffer.flush(dataStreamChannel); Mockito.verifyNoInteractions(dataStreamChannel); Assertions.assertEquals(0, outputBuffer.length()); Assertions.assertEquals(30, outputBuffer.capacity()); final byte[] tmp = "1234567890".getBytes(charset); outputBuffer.write(tmp, 0, tmp.length); outputBuffer.write(tmp, 0, tmp.length); outputBuffer.write('1'); outputBuffer.write('2'); Assertions.assertEquals(22, outputBuffer.length()); Assertions.assertEquals(8, outputBuffer.capacity()); Mockito.verifyNoInteractions(dataStreamChannel); } @Test void testFlush() throws Exception { final Charset charset = StandardCharsets.US_ASCII; final SharedOutputBuffer outputBuffer = new SharedOutputBuffer(30); final WritableByteChannelMock channel = new WritableByteChannelMock(1024); final DataStreamChannel dataStreamChannel = new DataStreamChannelMock(channel); outputBuffer.flush(dataStreamChannel); Assertions.assertEquals(0, outputBuffer.length()); Assertions.assertEquals(30, outputBuffer.capacity()); final byte[] tmp = "1234567890".getBytes(charset); outputBuffer.write(tmp, 0, tmp.length); outputBuffer.write(tmp, 0, tmp.length); outputBuffer.write('1'); outputBuffer.write('2'); outputBuffer.flush(dataStreamChannel); Assertions.assertEquals(0, outputBuffer.length()); Assertions.assertEquals(30, outputBuffer.capacity()); } @RepeatedTest(20) void testMultithreadingWriteStream() throws Exception { final Charset charset = StandardCharsets.US_ASCII; final SharedOutputBuffer outputBuffer = new SharedOutputBuffer(20); final WritableByteChannelMock channel = new WritableByteChannelMock(1024); final DataStreamChannelMock dataStreamChannel = new DataStreamChannelMock(channel); final ExecutorService executorService = Executors.newFixedThreadPool(2); try { final Future task1 = executorService.submit(() -> { final byte[] tmp = "1234567890".getBytes(charset); outputBuffer.write(tmp, 0, tmp.length); outputBuffer.write(tmp, 0, tmp.length); outputBuffer.write('1'); outputBuffer.write('2'); outputBuffer.write(tmp, 0, tmp.length); outputBuffer.write(tmp, 0, tmp.length); outputBuffer.write(tmp, 0, tmp.length); outputBuffer.writeCompleted(); outputBuffer.writeCompleted(); return Boolean.TRUE; }); final Future task2 = executorService.submit(() -> { for (;;) { outputBuffer.flush(dataStreamChannel); if (outputBuffer.isEndStream()) { break; } } return Boolean.TRUE; }); Assertions.assertEquals(Boolean.TRUE, task1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); Assertions.assertEquals(Boolean.TRUE, task2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); Assertions.assertEquals("1234567890123456789012123456789012345678901234567890", new String(channel.toByteArray(), charset)); } finally { executorService.shutdownNow(); } } @Test void testMultithreadingWriteStreamAbort() throws Exception { final Charset charset = StandardCharsets.US_ASCII; final SharedOutputBuffer outputBuffer = new SharedOutputBuffer(20); final ExecutorService executorService = Executors.newFixedThreadPool(2); final Future task1 = executorService.submit(() -> { final byte[] tmp = "1234567890".getBytes(charset); for (int i = 0; i < 20; i++) { outputBuffer.write(tmp, 0, tmp.length); } outputBuffer.writeCompleted(); return Boolean.TRUE; }); final Future task2 = executorService.submit(() -> { Thread.sleep(200); outputBuffer.abort(); return Boolean.TRUE; }); Assertions.assertEquals(Boolean.TRUE, task2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); try { task1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); } catch (final ExecutionException ex) { Assertions.assertTrue(ex.getCause() instanceof InterruptedIOException); } } @Test void testEndStreamOnlyCalledOnce() throws Exception { final SharedOutputBuffer outputBuffer = new SharedOutputBuffer(20); final WritableByteChannelMock channel = new WritableByteChannelMock(1024); final DataStreamChannelMock dataStreamChannel = Mockito.spy(new DataStreamChannelMock(channel)); final ExecutorService executorService = Executors.newFixedThreadPool(2); try { final Future task1 = executorService.submit(() -> { outputBuffer.writeCompleted(); return Boolean.TRUE; }); final Future task2 = executorService.submit(() -> { for (;;) { outputBuffer.flush(dataStreamChannel); if (outputBuffer.isEndStream()) { break; } } return Boolean.TRUE; }); Assertions.assertEquals(Boolean.TRUE, task1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); Assertions.assertEquals(Boolean.TRUE, task2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); Mockito.verify(dataStreamChannel, Mockito.times(1)).endStream(); } finally { executorService.shutdownNow(); } } } httpcore5/src/test/java/org/apache/hc/core5/http/protocol/0040755 0000000 0000000 00000000000 00000250600 022431 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestChainBuilder.java0100644 0000000 0000000 00000005140 00000250600 026462 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.util.LinkedList; import java.util.List; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestChainBuilder { @Test void testBuildChain() { final ChainBuilder cb = new ChainBuilder<>(); final HttpRequestInterceptor i1 = RequestContent.INSTANCE; final HttpRequestInterceptor i2 = RequestTargetHost.INSTANCE; final HttpRequestInterceptor i3 = RequestConnControl.INSTANCE; final HttpRequestInterceptor i4 = RequestUserAgent.INSTANCE; final HttpRequestInterceptor i5 = RequestExpectContinue.INSTANCE; cb.addFirst(i1); cb.addAllFirst(i2, i3); cb.addFirst(null); cb.addAllFirst((List) null); cb.addLast(i4); cb.addLast(null); cb.addAllLast(i5); cb.addAllLast((List) null); cb.addFirst(i1); cb.addAllLast(i3, i4, i5); final LinkedList list = cb.build(); Assertions.assertNotNull(list); Assertions.assertEquals(5, list.size()); Assertions.assertSame(i1, list.get(0)); Assertions.assertSame(i2, list.get(1)); Assertions.assertSame(i3, list.get(2)); Assertions.assertSame(i4, list.get(3)); Assertions.assertSame(i5, list.get(4)); } } httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestForwardedRequest.java0100644 0000000 0000000 00000020773 00000250600 027430 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.IOException; import java.net.InetSocketAddress; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EndpointDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.impl.BasicEntityDetails; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.net.URIAuthority; import org.junit.jupiter.api.Test; class TestForwardedRequest { private final HttpRequest request = new BasicHttpRequest("GET", "/"); private final HttpContext context = mock(HttpContext.class); private static final String FORWARDED_HEADER_NAME = "Forwarded"; @Test void testProcess() throws IOException, HttpException { final HttpRequestInterceptor processor = new ForwardedRequest(); // Create a mock endpoint with a remote address final InetSocketAddress remoteAddress = new InetSocketAddress("192.168.1.100", 12345); final InetSocketAddress localAddress = new InetSocketAddress("127.0.0.1", 2263); final EndpointDetails endpointDetails = mock(EndpointDetails.class); when(endpointDetails.getRemoteAddress()).thenReturn(remoteAddress); when(endpointDetails.getLocalAddress()).thenReturn(localAddress); // Create a mock HTTP request with a host and port final String host = "somehost"; final int port = 8888; request.setAuthority(new URIAuthority(host, port)); // Create a mock HTTP context with the endpoint and protocol version final ProtocolVersion version = HttpVersion.HTTP_1_1; final HttpCoreContext context = HttpCoreContext.create() ; context.setEndpointDetails(endpointDetails); // Process the HTTP request processor.process(request, new BasicEntityDetails(1, ContentType.APPLICATION_JSON), context); // Check the value of the Forwarded header final String expectedValue = "by=" + remoteAddress.getAddress().getHostAddress() + ":" + remoteAddress.getPort() + ";" + "for=" + localAddress.getAddress().getHostAddress() + ":" + localAddress.getPort() + ";" + "host=\"" + host + "\";port=" + port + ";proto=" + version.getProtocol(); assertEquals(expectedValue, request.getFirstHeader(FORWARDED_HEADER_NAME).getValue()); } @Test void testProcessWithIPv6() throws IOException, HttpException { final HttpRequestInterceptor processor = new ForwardedRequest(); // Create a mock endpoint with a remote IPv6 address final InetSocketAddress remoteAddress = InetSocketAddress.createUnresolved("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", 12345); final InetSocketAddress localAddress = InetSocketAddress.createUnresolved("[0:0:0:0:0:0:0:1]", 2263); final EndpointDetails endpointDetails = mock(EndpointDetails.class); when(endpointDetails.getRemoteAddress()).thenReturn(remoteAddress); when(endpointDetails.getLocalAddress()).thenReturn(localAddress); // Create a mock HTTP request with a host and port final String host = "somehost"; final int port = 8888; request.setAuthority(new URIAuthority(host, port)); // Create a mock HTTP context with the endpoint and protocol version final ProtocolVersion version = HttpVersion.HTTP_1_1; final HttpCoreContext context = HttpCoreContext.create() ; context.setEndpointDetails(endpointDetails); // Process the HTTP request processor.process(request, new BasicEntityDetails(1, ContentType.APPLICATION_JSON), context); // Check the value of the Forwarded header final String expectedValue = "by=" + remoteAddress.getHostName() + ":" + remoteAddress.getPort() + ";for=" + localAddress.getHostName() + ":" + localAddress.getPort() + ";host=\"" + host + "\";port=" + port + ";proto=" + version.getProtocol(); assertEquals(expectedValue, request.getFirstHeader(FORWARDED_HEADER_NAME).getValue()); } @Test void testProcess_withNullEndpointDetails_shouldAddValidHeader() throws Exception { final HttpRequestInterceptor processor = new ForwardedRequest(); // Create a mock HTTP request with a host and port final String host = "somehost"; final int port = 8888; request.setAuthority(new URIAuthority(host, port)); // Create a mock HTTP context with the endpoint and protocol version final ProtocolVersion version = HttpVersion.HTTP_1_1; final HttpCoreContext context = HttpCoreContext.create() ; context.setEndpointDetails(null); // Process the HTTP request processor.process(request, new BasicEntityDetails(1, ContentType.APPLICATION_JSON), context); // Check the value of the Forwarded header final String expectedValue = "host=\"" + host + "\";port=" + port + ";proto=" + version.getProtocol(); assertEquals(expectedValue, request.getFirstHeader(FORWARDED_HEADER_NAME).getValue()); } @Test void testWithForwardedHeader() throws Exception { final HttpRequestInterceptor processor = new ForwardedRequest(); // Create a mock HTTP request with a host and port final String host = "newhost"; final int port = 8888; request.setAuthority(new URIAuthority(host, port)); // Create a mock HTTP context with the endpoint and protocol version final ProtocolVersion version = HttpVersion.HTTP_1_1; final HttpCoreContext context = HttpCoreContext.create() ; context.setEndpointDetails(null); final String forwaredHeaderValue = "host=oldhost;port=8855;proto=HTTP"; request.setHeader(new BasicHeader(FORWARDED_HEADER_NAME, forwaredHeaderValue)); // Process the HTTP request processor.process(request, new BasicEntityDetails(1, ContentType.APPLICATION_JSON), context); // Check the value of the Forwarded header final String expectedValue = forwaredHeaderValue + ", " + "host=\"" + host + "\";port=" + port + ";proto=" + version.getProtocol(); assertEquals(expectedValue, request.getFirstHeader(FORWARDED_HEADER_NAME).getValue()); } @Test void testProcessWithNullHttpRequest() { final ForwardedRequest httpRequestModifier = new ForwardedRequest(); assertThrows(NullPointerException.class, () -> httpRequestModifier.process(null, null, context)); } @Test void testProcessWithNullHttpContext() { final ForwardedRequest httpRequestModifier = new ForwardedRequest(); assertThrows(NullPointerException.class, () -> httpRequestModifier.process(request, null, null)); } @Test void testProcessWithNullAuthority() { final ForwardedRequest httpRequestModifier = new ForwardedRequest(); assertThrows(ProtocolException.class, () -> httpRequestModifier.process(request, null, context)); } } httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestHttpExecutionContext.java0100644 0000000 0000000 00000006244 00000250600 030307 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; // the name of this test is historic, the implementation classes of HttpContext // have been renamed to BasicHttpContext and SyncBasicHttpContext class TestHttpExecutionContext { @Test void testContextOperations() { final HttpCoreContext parentContext = new HttpCoreContext(); final HttpCoreContext currentContext = new HttpCoreContext(parentContext); parentContext.setAttribute("param1", "1"); parentContext.setAttribute("param2", "2"); currentContext.setAttribute("param3", "3"); currentContext.setAttribute("param2", "4"); Assertions.assertEquals("1", parentContext.getAttribute("param1")); Assertions.assertEquals("2", parentContext.getAttribute("param2")); Assertions.assertNull(parentContext.getAttribute("param3")); Assertions.assertEquals("1", currentContext.getAttribute("param1")); Assertions.assertEquals("4", currentContext.getAttribute("param2")); Assertions.assertEquals("3", currentContext.getAttribute("param3")); Assertions.assertNull(currentContext.getAttribute("param4")); currentContext.removeAttribute("param1"); currentContext.removeAttribute("param2"); currentContext.removeAttribute("param3"); currentContext.removeAttribute("param4"); Assertions.assertEquals("1", currentContext.getAttribute("param1")); Assertions.assertEquals("2", currentContext.getAttribute("param2")); Assertions.assertNull(currentContext.getAttribute("param3")); Assertions.assertNull(currentContext.getAttribute("param4")); } @Test void testEmptyContextOperations() { final HttpCoreContext currentContext = new HttpCoreContext(); Assertions.assertNull(currentContext.getAttribute("param1")); currentContext.removeAttribute("param1"); Assertions.assertNull(currentContext.getAttribute("param1")); } } httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestRequestTE.java0100644 0000000 0000000 00000021007 00000250600 026012 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; 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; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestRequestTE { @Test void testValidTEHeader() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); context.setProtocolVersion(HttpVersion.HTTP_1_1); // Set the TE header and Connection header request.setHeader(HttpHeaders.TE, "trailers"); request.setHeader(HttpHeaders.CONNECTION, "TE"); final HttpRequestInterceptor interceptor = new RequestTE(); interceptor.process(request, request.getEntity(), context); assertNotNull(request.getHeader(HttpHeaders.TE)); assertEquals("trailers", request.getHeader(HttpHeaders.TE).getValue()); } @Test void testMultipleValidTEHeaders() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); context.setProtocolVersion(HttpVersion.HTTP_1_1); // Set both the TE header and the Connection header request.setHeader(HttpHeaders.TE, "trailers, deflate;q=0.5"); request.setHeader(HttpHeaders.CONNECTION, "TE"); final HttpRequestInterceptor interceptor = new RequestTE(); interceptor.process(request, request.getEntity(), context); assertNotNull(request.getHeader(HttpHeaders.TE)); assertEquals("trailers, deflate;q=0.5", request.getHeader(HttpHeaders.TE).getValue()); } @Test void testTEHeaderNotPresent() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); context.setProtocolVersion(HttpVersion.HTTP_1_1); final HttpRequestInterceptor interceptor = new RequestTE(); interceptor.process(request, request.getEntity(), context); // No TE header, no validation should occur assertNull(request.getHeader(HttpHeaders.TE)); } @Test void testTEHeaderContainsChunked() { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); context.setProtocolVersion(HttpVersion.HTTP_1_1); request.setHeader(HttpHeaders.TE, "chunked"); final HttpRequestInterceptor interceptor = new RequestTE(); Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request, request.getEntity(), context)); } @Test void testTEHeaderInvalidTransferCoding() { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); context.setProtocolVersion(HttpVersion.HTTP_1_1); request.setHeader(HttpHeaders.TE, "invalid;q=abc"); final HttpRequestInterceptor interceptor = new RequestTE(); Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request, request.getEntity(), context)); } @Test void testTEHeaderAlreadySet() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); context.setProtocolVersion(HttpVersion.HTTP_1_1); final String teValue = "trailers"; request.setHeader(HttpHeaders.TE, teValue); request.setHeader(HttpHeaders.CONNECTION, "TE"); // Add the Connection header as required final HttpRequestInterceptor interceptor = new RequestTE(); interceptor.process(request, request.getEntity(), context); assertEquals(HttpHeaders.TE, request.getHeader(HttpHeaders.TE).getName()); assertNotNull(request.getHeader(HttpHeaders.TE)); assertEquals(teValue, request.getHeader(HttpHeaders.TE).getValue()); } @Test void testTEHeaderWithConnectionHeaderValidation() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); context.setProtocolVersion(HttpVersion.HTTP_1_1); request.setHeader(HttpHeaders.TE, "trailers"); request.setHeader(HttpHeaders.CONNECTION, "TE"); final HttpRequestInterceptor interceptor = new RequestTE(); interceptor.process(request, request.getEntity(), context); assertEquals(HttpHeaders.TE, request.getHeader(HttpHeaders.TE).getName()); assertNotNull(request.getHeader(HttpHeaders.TE)); assertEquals("trailers", request.getHeader(HttpHeaders.TE).getValue()); } @Test void testTEHeaderWithoutConnectionHeaderThrowsException() { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); context.setProtocolVersion(HttpVersion.HTTP_1_1); request.setHeader(HttpHeaders.TE, "trailers"); final HttpRequestInterceptor interceptor = new RequestTE(); Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request, request.getEntity(), context)); } @Test void testTEHeaderWithoutTEInConnectionHeaderThrowsException() { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); context.setProtocolVersion(HttpVersion.HTTP_1_1); // Set TE header but Connection header does not include "TE" request.setHeader(HttpHeaders.TE, "trailers"); request.setHeader(HttpHeaders.CONNECTION, "keep-alive"); // Missing "TE" final HttpRequestInterceptor interceptor = new RequestTE(); Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request, request.getEntity(), context)); } @Test void testTEHeaderWithMultipleDirectivesInConnectionHeader() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); context.setProtocolVersion(HttpVersion.HTTP_1_1); // Set TE header and a Connection header with multiple directives request.setHeader(HttpHeaders.TE, "trailers"); request.setHeader(HttpHeaders.CONNECTION, "keep-alive, close, TE"); final HttpRequestInterceptor interceptor = new RequestTE(); interceptor.process(request, request.getEntity(), context); assertNotNull(request.getHeader(HttpHeaders.CONNECTION)); assertTrue(request.getHeader(HttpHeaders.CONNECTION).getValue().contains("TE")); } } httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestStandardInterceptors.java0100644 0000000 0000000 00000163021 00000250600 030276 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import java.nio.charset.StandardCharsets; import javax.net.ssl.SSLSession; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.MisdirectedRequestException; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.io.entity.BasicHttpEntity; import org.apache.hc.core5.http.io.entity.EmptyInputStream; import org.apache.hc.core5.http.io.entity.HttpEntities; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.net.URIAuthority; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; class TestStandardInterceptors { @Test void testRequestConnControlGenerated() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); final HttpRequestInterceptor interceptor = RequestConnControl.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.CONNECTION); Assertions.assertNotNull(header); Assertions.assertEquals("keep-alive", header.getValue()); } @Test void testRequestConnControlConnectMethod() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.CONNECT, "/"); final HttpRequestInterceptor interceptor = RequestConnControl.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.CONNECTION); Assertions.assertNull(header); } @Test void testRequestConnControlCustom() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); final Header myheader = new BasicHeader(HttpHeaders.CONNECTION, "close"); request.addHeader(myheader); final HttpRequestInterceptor interceptor = RequestConnControl.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.CONNECTION); Assertions.assertNotNull(header); Assertions.assertEquals("close", header.getValue()); Assertions.assertSame(header, myheader); } @Test void testRequestConnControlUpgrade() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.addHeader(HttpHeaders.UPGRADE, "HTTP/2"); final HttpRequestInterceptor interceptor = RequestConnControl.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.CONNECTION); Assertions.assertNotNull(header); Assertions.assertEquals("upgrade", header.getValue()); } @Test void testRequestConnControlInvalidInput() { final HttpRequestInterceptor interceptor = RequestConnControl.INSTANCE; Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); } @Test void testRequestContentProtocolException() { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/"); request1.addHeader(new BasicHeader(HttpHeaders.TRANSFER_ENCODING, "chunked")); final BasicClassicHttpRequest request2 = new BasicClassicHttpRequest(Method.POST, "/"); request2.addHeader(new BasicHeader(HttpHeaders.CONTENT_LENGTH, "12")); final HttpRequestInterceptor interceptor = RequestContent.INSTANCE; Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request1, request1.getEntity(), context)); Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request2, request2.getEntity(), context)); } @Test void testRequestContentNullEntity() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); final HttpRequestInterceptor interceptor = RequestContent.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.CONTENT_LENGTH); Assertions.assertNotNull(header); Assertions.assertEquals(0, Integer.parseInt(header.getValue())); Assertions.assertNull(request.getFirstHeader(HttpHeaders.TRANSFER_ENCODING)); } @Test void testRequestContentNullEntityNonEnclosingMethod() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); final HttpRequestInterceptor interceptor = RequestContent.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.CONTENT_LENGTH); Assertions.assertNull(header); Assertions.assertNull(request.getFirstHeader(HttpHeaders.TRANSFER_ENCODING)); } @Test void testRequestContentEntityContentLengthDelimitedHTTP11() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.setEntity(new StringEntity("whatever", StandardCharsets.US_ASCII)); final HttpRequestInterceptor interceptor = RequestContent.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.CONTENT_LENGTH); Assertions.assertNotNull(header); Assertions.assertEquals(8, Integer.parseInt(header.getValue())); Assertions.assertNull(request.getFirstHeader(HttpHeaders.TRANSFER_ENCODING)); } @Test void testRequestContentEntityChunkedHTTP11() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.setEntity(new StringEntity("whatever", StandardCharsets.US_ASCII, true)); final HttpRequestInterceptor interceptor = RequestContent.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.TRANSFER_ENCODING); Assertions.assertNotNull(header); Assertions.assertEquals("chunked", header.getValue()); Assertions.assertNull(request.getFirstHeader(HttpHeaders.CONTENT_LENGTH)); } @Test void testRequestContentEntityUnknownLengthHTTP11() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.setEntity(new BasicHttpEntity(EmptyInputStream.INSTANCE, -1, null)); final HttpRequestInterceptor interceptor = RequestContent.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.TRANSFER_ENCODING); Assertions.assertNotNull(header); Assertions.assertEquals("chunked", header.getValue()); Assertions.assertNull(request.getFirstHeader(HttpHeaders.CONTENT_LENGTH)); } @Test void testRequestContentEntityChunkedHTTP10() { final HttpCoreContext context = HttpCoreContext.create(); context.setProtocolVersion(HttpVersion.HTTP_1_0); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.setEntity(new StringEntity("whatever", StandardCharsets.US_ASCII, true)); final HttpRequestInterceptor interceptor = RequestContent.INSTANCE; Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request, request.getEntity(), context)); } @Test void testRequestContentTypeAndEncoding() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.setEntity(new BasicHttpEntity(EmptyInputStream.INSTANCE, ContentType.parseLenient("whatever"), "whatever")); final HttpRequestInterceptor interceptor = RequestContent.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header h1 = request.getFirstHeader(HttpHeaders.CONTENT_TYPE); Assertions.assertNotNull(h1); Assertions.assertEquals("whatever", h1.getValue()); final Header h2 = request.getFirstHeader(HttpHeaders.CONTENT_ENCODING); Assertions.assertNotNull(h2); Assertions.assertEquals("whatever", h2.getValue()); } @Test void testRequestContentNullTypeAndEncoding() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.setEntity(new BasicHttpEntity(EmptyInputStream.INSTANCE, null, null)); final HttpRequestInterceptor interceptor = RequestContent.INSTANCE; interceptor.process(request, request.getEntity(), context); Assertions.assertNull(request.getFirstHeader(HttpHeaders.CONTENT_TYPE)); Assertions.assertNull(request.getFirstHeader(HttpHeaders.CONTENT_ENCODING)); } @Test void testRequestContentEntityUnknownLengthHTTP10() { final HttpCoreContext context = HttpCoreContext.create(); context.setProtocolVersion(HttpVersion.HTTP_1_0); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.setEntity(new BasicHttpEntity(EmptyInputStream.INSTANCE, -1, null)); final HttpRequestInterceptor interceptor = RequestContent.INSTANCE; Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request, request.getEntity(), context)); } @Test void testRequestContentInvalidInput() { final HttpRequestInterceptor interceptor = RequestContent.INSTANCE; Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); } @Test void testRequestContentIgnoreNonenclosingRequests() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); final HttpRequestInterceptor interceptor = RequestContent.INSTANCE; interceptor.process(request, request.getEntity(), context); Assertions.assertEquals(1, request.getHeaders().length); } @Test void testRequestContentOverwriteHeaders() throws Exception { final RequestContent interceptor = new RequestContent(true); final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.addHeader(new BasicHeader(HttpHeaders.CONTENT_LENGTH, "10")); request.addHeader(new BasicHeader(HttpHeaders.TRANSFER_ENCODING, "whatever")); request.setEntity(new StringEntity("")); interceptor.process(request, request.getEntity(), context); final Header h1 = request.getFirstHeader(HttpHeaders.CONTENT_LENGTH); Assertions.assertNotNull(h1); Assertions.assertEquals("0", h1.getValue()); } @Test void testRequestContentAddHeaders() throws Exception { final RequestContent interceptor = new RequestContent(true); final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.setEntity(new StringEntity("")); interceptor.process(request, request.getEntity(), context); final Header h1 = request.getFirstHeader(HttpHeaders.CONTENT_LENGTH); Assertions.assertNotNull(h1); Assertions.assertEquals("0", h1.getValue()); final Header h2 = request.getFirstHeader(HttpHeaders.TRANSFER_ENCODING); Assertions.assertNull(h2); } @Test void testRequestContentEntityWithTrailers() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.setEntity(HttpEntities.create("whatever", StandardCharsets.US_ASCII, new BasicHeader("h1", "this"), new BasicHeader("h1", "that"), new BasicHeader("h2", "this and that"))); final HttpRequestInterceptor interceptor = RequestContent.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header1 = request.getFirstHeader(HttpHeaders.TRANSFER_ENCODING); Assertions.assertNotNull(header1); final Header header2 = request.getFirstHeader(HttpHeaders.TRAILER); Assertions.assertNotNull(header2); Assertions.assertEquals("h1, h2", header2.getValue()); } @Test void testRequestContentTraceWithEntity() { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.TRACE, "/"); request.setEntity(new StringEntity("stuff")); final HttpRequestInterceptor interceptor = RequestContent.INSTANCE; Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request, request.getEntity(), context)); } @Test void testRequestExpectContinueGenerated() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.setEntity(new StringEntity("whatever", StandardCharsets.US_ASCII)); final RequestExpectContinue interceptor = RequestExpectContinue.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.EXPECT); Assertions.assertNotNull(header); Assertions.assertEquals(HeaderElements.CONTINUE, header.getValue()); } @Test void testRequestExpectContinueHTTP10() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); context.setProtocolVersion(HttpVersion.HTTP_1_0); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.setEntity(new StringEntity("whatever", StandardCharsets.US_ASCII)); final RequestExpectContinue interceptor = RequestExpectContinue.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.EXPECT); Assertions.assertNull(header); } @Test void testRequestExpectContinueZeroContent() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.setEntity(new StringEntity("", StandardCharsets.US_ASCII)); final RequestExpectContinue interceptor = RequestExpectContinue.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.EXPECT); Assertions.assertNull(header); } @Test void testRequestExpectContinueInvalidInput() { final RequestExpectContinue interceptor = RequestExpectContinue.INSTANCE; Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); } @Test void testRequestExpectContinueIgnoreNonenclosingRequests() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); final RequestExpectContinue interceptor = RequestExpectContinue.INSTANCE; interceptor.process(request, request.getEntity(), context); Assertions.assertEquals(0, request.getHeaders().length); } @Test void testRequestTargetHostGenerated() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.setAuthority(new URIAuthority("somehost", 8080)); final HttpRequestInterceptor interceptor = RequestTargetHost.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.HOST); Assertions.assertNotNull(header); Assertions.assertEquals("somehost:8080", header.getValue()); } @Test void testRequestTargetHostNotGenerated() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.setAuthority(new URIAuthority("somehost", 8080)); request.addHeader(new BasicHeader(HttpHeaders.HOST, "whatever")); final HttpRequestInterceptor interceptor = RequestTargetHost.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.HOST); Assertions.assertNotNull(header); Assertions.assertEquals("whatever", header.getValue()); } @Test void testRequestTargetHostMissingHostHTTP10() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); context.setProtocolVersion(HttpVersion.HTTP_1_0); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); final HttpRequestInterceptor interceptor = RequestTargetHost.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.HOST); Assertions.assertNull(header); } @Test void testRequestTargetHostMissingHostHTTP11() { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); final HttpRequestInterceptor interceptor = RequestTargetHost.INSTANCE; Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request, request.getEntity(), context)); } @Test void testRequestTargetHostInvalidInput() { final HttpRequestInterceptor interceptor = RequestTargetHost.INSTANCE; Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(new BasicClassicHttpRequest(Method.GET, "/"), null, null)); } @Test void testRequestTargetHostConnectHttp11() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.CONNECT, "/"); request.setAuthority(new URIAuthority("somehost", 8080)); final HttpRequestInterceptor interceptor = RequestTargetHost.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.HOST); Assertions.assertNotNull(header); Assertions.assertEquals("somehost:8080", header.getValue()); } @Test void testRequestTargetHostConnectHttp10() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); context.setProtocolVersion(HttpVersion.HTTP_1_0); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.CONNECT, "/"); request.setAuthority(new URIAuthority("somehost", 8080)); final HttpRequestInterceptor interceptor = RequestTargetHost.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.HOST); Assertions.assertNull(header); } @Test void testTEHeaderWithConnectionTE() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); context.setProtocolVersion(HttpVersion.HTTP_1_1); // Set both TE and Connection headers as per the requirement request.setHeader(HttpHeaders.TE, "trailers"); request.setHeader(HttpHeaders.CONNECTION, "TE"); final RequestTE interceptor = new RequestTE(); interceptor.process(request, request.getEntity(), context); final Header connectionHeader = request.getFirstHeader(HttpHeaders.CONNECTION); Assertions.assertNotNull(connectionHeader); Assertions.assertEquals("TE", connectionHeader.getValue()); } @Test void testRequestUserAgentGenerated() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); final RequestUserAgent interceptor = new RequestUserAgent("some agent"); interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.USER_AGENT); Assertions.assertNotNull(header); Assertions.assertEquals("some agent", header.getValue()); } @Test void testRequestUserAgentNotGenerated() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.addHeader(new BasicHeader(HttpHeaders.USER_AGENT, "whatever")); final RequestUserAgent interceptor = new RequestUserAgent("some agent"); interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.USER_AGENT); Assertions.assertNotNull(header); Assertions.assertEquals("whatever", header.getValue()); } @Test void testRequestUserAgentMissingUserAgent() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); final HttpRequestInterceptor interceptor = RequestUserAgent.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.USER_AGENT); Assertions.assertNull(header); } @Test void testRequestUserAgentInvalidInput() { final HttpRequestInterceptor interceptor = RequestUserAgent.INSTANCE; Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); } @Test void testResponseConnControlNoEntity() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); final ResponseConnControl interceptor = new ResponseConnControl(); interceptor.process(response, response.getEntity(), context); final Header header = response.getFirstHeader(HttpHeaders.CONNECTION); Assertions.assertNull(header); } @Test void testResponseConnControlEntityContentLength() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setEntity(new StringEntity("whatever")); final ResponseConnControl interceptor = new ResponseConnControl(); interceptor.process(response, response.getEntity(), context); final Header header = response.getFirstHeader(HttpHeaders.CONNECTION); Assertions.assertNull(header); } @Test void testResponseConnControlEntityUnknownContentLengthExplicitKeepAlive() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.addHeader(new BasicHeader(HttpHeaders.CONNECTION, "keep-alive")); context.setRequest(request); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setEntity(new BasicHttpEntity(EmptyInputStream.INSTANCE, null)); final ResponseConnControl interceptor = new ResponseConnControl(); interceptor.process(response, response.getEntity(), context); final Header header = response.getFirstHeader(HttpHeaders.CONNECTION); Assertions.assertNotNull(header); Assertions.assertEquals("keep-alive", header.getValue()); } @Test void testResponseConnControlEntityChunked() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setEntity(new BasicHttpEntity(EmptyInputStream.INSTANCE, null, true)); final ResponseConnControl interceptor = new ResponseConnControl(); interceptor.process(response, response.getEntity(), context); final Header header = response.getFirstHeader(HttpHeaders.CONNECTION); Assertions.assertNull(header); } @Test void testResponseConnControlEntityUnknownContentLengthHTTP10() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); context.setProtocolVersion(HttpVersion.HTTP_1_0); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.addHeader(new BasicHeader(HttpHeaders.CONNECTION, "keep-alive")); context.setRequest(request); final BasicClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setEntity(new BasicHttpEntity(EmptyInputStream.INSTANCE, null)); final ResponseConnControl interceptor = new ResponseConnControl(); interceptor.process(response, response.getEntity(), context); final Header header = response.getFirstHeader(HttpHeaders.CONNECTION); Assertions.assertNotNull(header); Assertions.assertEquals("close", header.getValue()); } @Test void testResponseConnControlClientRequest() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.addHeader(new BasicHeader(HttpHeaders.CONNECTION, "keep-alive")); context.setRequest(request); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setEntity(new StringEntity("whatever")); final ResponseConnControl interceptor = new ResponseConnControl(); interceptor.process(response, response.getEntity(), context); final Header header = response.getFirstHeader(HttpHeaders.CONNECTION); Assertions.assertNotNull(header); Assertions.assertEquals("keep-alive", header.getValue()); } @Test void testResponseConnControlClientRequest2() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); context.setRequest(request); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setEntity(new StringEntity("whatever")); final ResponseConnControl interceptor = new ResponseConnControl(); interceptor.process(response, response.getEntity(), context); final Header header = response.getFirstHeader(HttpHeaders.CONNECTION); Assertions.assertNull(header); } @Test void testResponseConnControl10Client11Response() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); context.setProtocolVersion(HttpVersion.HTTP_1_0); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); context.setRequest(request); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setEntity(new StringEntity("whatever")); final ResponseConnControl interceptor = new ResponseConnControl(); interceptor.process(response, response.getEntity(), context); final Header header = response.getFirstHeader(HttpHeaders.CONNECTION); Assertions.assertNotNull(header); Assertions.assertEquals("close", header.getValue()); } @Test void testResponseConnControlStatusCode() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.addHeader(new BasicHeader(HttpHeaders.CONNECTION, "keep-alive")); context.setRequest(request); final ResponseConnControl interceptor = new ResponseConnControl(); final int [] statusCodes = new int[] { HttpStatus.SC_BAD_REQUEST, HttpStatus.SC_REQUEST_TIMEOUT, HttpStatus.SC_LENGTH_REQUIRED, HttpStatus.SC_REQUEST_TOO_LONG, HttpStatus.SC_REQUEST_URI_TOO_LONG, HttpStatus.SC_SERVICE_UNAVAILABLE, HttpStatus.SC_NOT_IMPLEMENTED }; for (final int statusCode : statusCodes) { final ClassicHttpResponse response = new BasicClassicHttpResponse(statusCode, "Unreasonable"); interceptor.process(response, response.getEntity(), context); final Header header = response.getFirstHeader(HttpHeaders.CONNECTION); Assertions.assertNotNull(header); Assertions.assertEquals("close", header.getValue()); } } @Test void testResponseConnControlExplicitClose() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.addHeader(new BasicHeader(HttpHeaders.CONNECTION, "keep-alive")); context.setRequest(request); final ResponseConnControl interceptor = new ResponseConnControl(); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); response.setHeader(HttpHeaders.CONNECTION, "close"); interceptor.process(response, response.getEntity(), context); final Header header = response.getFirstHeader(HttpHeaders.CONNECTION); Assertions.assertNotNull(header); Assertions.assertEquals("close", header.getValue()); } @Test void testResponseConnControlClientRequestMixUp() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.addHeader(new BasicHeader(HttpHeaders.CONNECTION, "blah, keep-alive, close")); context.setRequest(request); final ResponseConnControl interceptor = new ResponseConnControl(); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); interceptor.process(response, response.getEntity(), context); final Header header = response.getFirstHeader(HttpHeaders.CONNECTION); Assertions.assertNotNull(header); Assertions.assertEquals("close", header.getValue()); } @Test void testResponseConnControlUpgrade() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ResponseConnControl interceptor = new ResponseConnControl(); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); response.addHeader(HttpHeaders.UPGRADE, "HTTP/2"); interceptor.process(response, response.getEntity(), context); final Header header = response.getFirstHeader(HttpHeaders.CONNECTION); Assertions.assertNotNull(header); Assertions.assertEquals("upgrade", header.getValue()); } @Test void testResponseConnControlHostInvalidInput() { final ResponseConnControl interceptor = new ResponseConnControl(); Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(response, null, null)); } @Test void testResponseContentNoEntity() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); final ResponseContent interceptor = new ResponseContent(); interceptor.process(response, response.getEntity(), context); final Header header = response.getFirstHeader(HttpHeaders.CONTENT_LENGTH); Assertions.assertNotNull(header); Assertions.assertEquals("0", header.getValue()); } @Test void testResponseContentStatusNoContent() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setCode(HttpStatus.SC_NO_CONTENT); final ResponseContent interceptor = new ResponseContent(); interceptor.process(response, response.getEntity(), context); final Header header = response.getFirstHeader(HttpHeaders.CONTENT_LENGTH); Assertions.assertNull(header); } @Test void testResponseContentStatusNotModified() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setCode(HttpStatus.SC_NOT_MODIFIED); final ResponseContent interceptor = new ResponseContent(); interceptor.process(response, response.getEntity(), context); final Header header = response.getFirstHeader(HttpHeaders.CONTENT_LENGTH); Assertions.assertNull(header); } @Test void testResponseContentEntityChunked() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setEntity(new BasicHttpEntity(EmptyInputStream.INSTANCE, null, true)); final ResponseContent interceptor = new ResponseContent(); interceptor.process(response, response.getEntity(), context); final Header h1 = response.getFirstHeader(HttpHeaders.TRANSFER_ENCODING); Assertions.assertNotNull(h1); Assertions.assertEquals("chunked", h1.getValue()); final Header h2 = response.getFirstHeader(HttpHeaders.CONTENT_LENGTH); Assertions.assertNull(h2); } @Test void testResponseContentEntityContentLenghtDelimited() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setEntity(new BasicHttpEntity(EmptyInputStream.INSTANCE, 10, null)); final ResponseContent interceptor = new ResponseContent(); interceptor.process(response, response.getEntity(), context); final Header h1 = response.getFirstHeader(HttpHeaders.CONTENT_LENGTH); Assertions.assertNotNull(h1); Assertions.assertEquals("10", h1.getValue()); final Header h2 = response.getFirstHeader(HttpHeaders.TRANSFER_ENCODING); Assertions.assertNull(h2); } @Test void testResponseContentEntityUnknownContentLength() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setEntity(new BasicHttpEntity(EmptyInputStream.INSTANCE, null)); final ResponseContent interceptor = new ResponseContent(); interceptor.process(response, response.getEntity(), context); final Header h1 = response.getFirstHeader(HttpHeaders.TRANSFER_ENCODING); Assertions.assertNotNull(h1); Assertions.assertEquals("chunked", h1.getValue()); final Header h2 = response.getFirstHeader(HttpHeaders.CONTENT_LENGTH); Assertions.assertNull(h2); } @Test void testResponseContentEntityChunkedHTTP10() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); context.setProtocolVersion(HttpVersion.HTTP_1_0); final BasicClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setEntity(new BasicHttpEntity(EmptyInputStream.INSTANCE, null, true)); final ResponseContent interceptor = new ResponseContent(); interceptor.process(response, response.getEntity(), context); final Header h1 = response.getFirstHeader(HttpHeaders.TRANSFER_ENCODING); Assertions.assertNull(h1); final Header h2 = response.getFirstHeader(HttpHeaders.CONTENT_LENGTH); Assertions.assertNull(h2); } @Test void testResponseContentEntityNoContentTypeAndEncoding() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setEntity(new BasicHttpEntity(EmptyInputStream.INSTANCE, null)); final ResponseContent interceptor = new ResponseContent(); interceptor.process(response, response.getEntity(), context); final Header h1 = response.getFirstHeader(HttpHeaders.CONTENT_TYPE); Assertions.assertNull(h1); final Header h2 = response.getFirstHeader(HttpHeaders.CONTENT_ENCODING); Assertions.assertNull(h2); } @Test void testResponseContentEntityContentTypeAndEncoding() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setEntity(new BasicHttpEntity(EmptyInputStream.INSTANCE, ContentType.parseLenient("whatever"), "whatever")); final ResponseContent interceptor = new ResponseContent(); interceptor.process(response, response.getEntity(), context); final Header h1 = response.getFirstHeader(HttpHeaders.CONTENT_TYPE); Assertions.assertNotNull(h1); Assertions.assertEquals("whatever", h1.getValue()); final Header h2 = response.getFirstHeader(HttpHeaders.CONTENT_ENCODING); Assertions.assertNotNull(h2); Assertions.assertEquals("whatever", h2.getValue()); } @Test void testResponseContentInvalidInput() { final ResponseContent interceptor = new ResponseContent(); Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); } @Test void testResponseContentInvalidResponseState() { final ResponseContent interceptor = new ResponseContent(); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response1 = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response1.addHeader(new BasicHeader(HttpHeaders.CONTENT_LENGTH, "10")); Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(response1, response1.getEntity(), context)); final ClassicHttpResponse response2 = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response2.addHeader(new BasicHeader(HttpHeaders.TRANSFER_ENCODING, "stuff")); Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(response2, response2.getEntity(), context)); } @Test void testResponseContentOverwriteHeaders() throws Exception { final ResponseContent interceptor = new ResponseContent(true); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.addHeader(new BasicHeader(HttpHeaders.CONTENT_LENGTH, "10")); response.addHeader(new BasicHeader(HttpHeaders.TRANSFER_ENCODING, "whatever")); interceptor.process(response, response.getEntity(), context); Assertions.assertEquals("0", response.getFirstHeader(HttpHeaders.CONTENT_LENGTH).getValue()); } @Test void testResponseContentAddHeaders() throws Exception { final ResponseContent interceptor = new ResponseContent(true); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); interceptor.process(response, response.getEntity(), context); Assertions.assertEquals("0", response.getFirstHeader(HttpHeaders.CONTENT_LENGTH).getValue()); Assertions.assertNull(response.getFirstHeader(HttpHeaders.TRANSFER_ENCODING)); } @Test void testResponseContentEntityWithTrailers() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setEntity(HttpEntities.create("whatever", StandardCharsets.US_ASCII, new BasicHeader("h1", "this"), new BasicHeader("h1", "that"), new BasicHeader("h2", "this and that"))); final ResponseContent interceptor = new ResponseContent(); interceptor.process(response, response.getEntity(), context); final Header header1 = response.getFirstHeader(HttpHeaders.TRANSFER_ENCODING); Assertions.assertNotNull(header1); final Header header2 = response.getFirstHeader(HttpHeaders.TRAILER); Assertions.assertNotNull(header2); Assertions.assertEquals("h1, h2", header2.getValue()); } @Test void testResponseDateGenerated() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); final ResponseDate interceptor = new ResponseDate(); interceptor.process(response, response.getEntity(), context); final Header h1 = response.getFirstHeader(HttpHeaders.DATE); Assertions.assertNotNull(h1); interceptor.process(response, response.getEntity(), context); final Header h2 = response.getFirstHeader(HttpHeaders.DATE); Assertions.assertNotNull(h2); } @Test void testResponseDateInvalidInput() { final ResponseDate interceptor = new ResponseDate(); Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); } @Test void testRequestDateGenerated() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.setEntity(new StringEntity("stuff")); final HttpRequestInterceptor interceptor = RequestDate.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header h1 = request.getFirstHeader(HttpHeaders.DATE); Assertions.assertNotNull(h1); interceptor.process(request, request.getEntity(), context); final Header h2 = request.getFirstHeader(HttpHeaders.DATE); Assertions.assertNotNull(h2); } @Test void testRequestDateNotGenerated() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); final HttpRequestInterceptor interceptor = RequestDate.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header h1 = request.getFirstHeader(HttpHeaders.DATE); Assertions.assertNull(h1); } @Test void testRequestDateInvalidInput() { final HttpRequestInterceptor interceptor = RequestDate.INSTANCE; Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); } @Test void testResponseServerGenerated() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); final ResponseServer interceptor = new ResponseServer("some server"); interceptor.process(response, response.getEntity(), context); final Header h1 = response.getFirstHeader(HttpHeaders.SERVER); Assertions.assertNotNull(h1); Assertions.assertEquals("some server", h1.getValue()); } @Test void testResponseServerNotGenerated() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.addHeader(new BasicHeader(HttpHeaders.SERVER, "whatever")); final ResponseServer interceptor = new ResponseServer("some server"); interceptor.process(response, response.getEntity(), context); final Header h1 = response.getFirstHeader(HttpHeaders.SERVER); Assertions.assertNotNull(h1); Assertions.assertEquals("whatever", h1.getValue()); } @Test void testResponseServerMissing() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); final ResponseServer interceptor = new ResponseServer(); interceptor.process(response, response.getEntity(), context); final Header h1 = response.getFirstHeader(HttpHeaders.SERVER); Assertions.assertNull(h1); } @Test void testResponseServerInvalidInput() { final ResponseServer interceptor = new ResponseServer(); Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); } @Test void testRequestHttp10HostHeaderAbsent() { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.setVersion(HttpVersion.HTTP_1_0); final RequestValidateHost interceptor = new RequestValidateHost(); Assertions.assertDoesNotThrow(() -> interceptor.process(request, request.getEntity(), context)); } @Test void testRequestHttpHostHeader() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.setVersion(HttpVersion.HTTP_1_1); request.setHeader(HttpHeaders.HOST, "host:8888"); final RequestValidateHost interceptor = new RequestValidateHost(); interceptor.process(request, request.getEntity(), context); Assertions.assertEquals(new URIAuthority("host", 8888), request.getAuthority()); } @Test void testRequestHttpHostHeaderNoPort() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.setVersion(HttpVersion.HTTP_1_1); request.setHeader(HttpHeaders.HOST, "host"); final RequestValidateHost interceptor = new RequestValidateHost(); interceptor.process(request, request.getEntity(), context); Assertions.assertEquals(new URIAuthority("host"), request.getAuthority()); } @Test void testRequestHttp11HostHeaderPresent() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.setHeader(HttpHeaders.HOST, "blah"); final RequestValidateHost interceptor = new RequestValidateHost(); interceptor.process(request, request.getEntity(), context); } @Test void testRequestHttp11HostHeaderAbsent() { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); final RequestValidateHost interceptor = new RequestValidateHost(); Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request, request.getEntity(), context)); } @Test void testRequestHttp11MultipleHostHeaders() { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.addHeader(HttpHeaders.HOST, "blah"); request.addHeader(HttpHeaders.HOST, "blah"); final RequestValidateHost interceptor = new RequestValidateHost(); Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request, request.getEntity(), context)); } @Test void testRequestAbsoluteRequestURITakesPrecedenceOverHostHeader() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "https://somehost/blah?huh"); request.setHeader(HttpHeaders.HOST, "blah"); final RequestValidateHost interceptor = new RequestValidateHost(); interceptor.process(request, request.getEntity(), context); Assertions.assertEquals(new URIAuthority("somehost"), request.getAuthority()); } @Test void testRequestConformance() { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.setScheme("http"); request.setAuthority(new URIAuthority("somehost", 8888)); request.setPath("/path"); final RequestConformance interceptor = new RequestConformance(); Assertions.assertDoesNotThrow(() -> interceptor.process(request, request.getEntity(), context)); } @Test void testRequestConformanceSchemeMissing() { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.setAuthority(new URIAuthority("somehost", 8888)); request.setPath("/path"); final RequestConformance interceptor = new RequestConformance(); Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request, request.getEntity(), context)); } @Test void testRequestConformancePathMissing() { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.setScheme("http"); request.setAuthority(new URIAuthority("somehost", 8888)); request.setPath(""); final RequestConformance interceptor = new RequestConformance(); Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request, request.getEntity(), context)); } @Test void testRequestConformanceHostMissing() { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.setScheme("http"); request.setAuthority(new URIAuthority("", -1)); request.setPath("/path"); final RequestConformance interceptor = new RequestConformance(); Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request, request.getEntity(), context)); } @Test void testRequestConformanceHttps() { final HttpCoreContext context = HttpCoreContext.create(); context.setSSLSession(Mockito.mock(SSLSession.class)); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.setScheme("https"); request.setAuthority(new URIAuthority("somehost", 8888)); request.setPath("/path"); final RequestConformance interceptor = new RequestConformance(); Assertions.assertDoesNotThrow(() -> interceptor.process(request, request.getEntity(), context)); } @Test void testRequestConformanceHttpsInsecureConnection() { final HttpCoreContext context = HttpCoreContext.create(); context.setSSLSession(null); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.setScheme("https"); request.setAuthority(new URIAuthority("somehost", 8888)); request.setPath("/path"); final RequestConformance interceptor = new RequestConformance(); Assertions.assertThrows(MisdirectedRequestException.class, () -> interceptor.process(request, request.getEntity(), context)); } @Test void testResponseConformanceNoContent() { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content"); final ResponseConformance interceptor = new ResponseConformance(); Assertions.assertDoesNotThrow(() -> interceptor.process(response, response.getEntity(), context)); } @Test void testResponseConformanceNotModified() { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified"); final ResponseConformance interceptor = new ResponseConformance(); Assertions.assertDoesNotThrow(() -> interceptor.process(response, response.getEntity(), context)); } @Test void testResponseConformanceNoContentWithEntity() { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content"); response.setEntity(new StringEntity("stuff")); final ResponseConformance interceptor = new ResponseConformance(); Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(response, response.getEntity(), context)); } @Test void testResponseConformanceNotModifiedWithEntity() { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified"); response.setEntity(new StringEntity("stuff")); final ResponseConformance interceptor = new ResponseConformance(); Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(response, response.getEntity(), context)); } @Test void testInvalidDateReplaced() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); // Add an invalid Date header response.setHeader(HttpHeaders.DATE, "Invalid Date"); // Instantiate the ResponseDate interceptor with replaceInvalidDate set to true final ResponseDate interceptor = new ResponseDate(true); // Process the response, which should replace the invalid date interceptor.process(response, response.getEntity(), context); // Assert that the Date header has been replaced final Header newDateHeader = response.getFirstHeader(HttpHeaders.DATE); Assertions.assertNotNull(newDateHeader); Assertions.assertNotEquals("Invalid Date", newDateHeader.getValue()); } } httpcore5/src/test/java/org/apache/hc/core5/http/protocol/ViaRequestTest.java0100644 0000000 0000000 00000012010 00000250600 026213 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.protocol; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.net.URIAuthority; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class ViaRequestTest { @Test void testViaRequestGenerated() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.setAuthority(new URIAuthority("somehost", 8888)); context.setProtocolVersion(HttpVersion.HTTP_1_1); final ViaRequest interceptor = new ViaRequest(); interceptor.process(request, request.getEntity(), context); assertEquals(HttpHeaders.VIA, request.getHeader(HttpHeaders.VIA).getName()); assertNotNull(request.getHeader(HttpHeaders.VIA)); assertEquals("HTTP 1.1 somehost:8888", request.getHeader(HttpHeaders.VIA).getValue()); } @Test void testViaRequestGeneratedWithOutPort() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.setAuthority(new URIAuthority("somehost")); context.setProtocolVersion(HttpVersion.HTTP_1_1); final ViaRequest interceptor = new ViaRequest(); interceptor.process(request, request.getEntity(), context); assertEquals(HttpHeaders.VIA, request.getHeader(HttpHeaders.VIA).getName()); assertNotNull(request.getHeader(HttpHeaders.VIA)); assertEquals("HTTP 1.1 somehost", request.getHeader(HttpHeaders.VIA).getValue()); } @Test void testViaRequestInvalidHttpVersion() { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); context.setProtocolVersion(HttpVersion.HTTP_0_9); request.setAuthority(new URIAuthority("somehost", 8888)); final HttpRequestInterceptor interceptor = ViaRequest.INSTANCE; Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request, request.getEntity(), context)); } @Test void testViaRequestInvalidAuthority() { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); context.setProtocolVersion(HttpVersion.HTTP_1_1); final HttpRequestInterceptor interceptor = ViaRequest.INSTANCE; Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request, request.getEntity(), context)); } @Test void testViaRequestNotCreatedAlreadyAdded() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); context.setProtocolVersion(HttpVersion.HTTP_1_1); request.setAuthority(new URIAuthority("somehost", 8888)); final String viaValue = "HTTP 1.1 host:8888"; request.setHeader(HttpHeaders.VIA, viaValue); final ViaRequest interceptor = new ViaRequest(); interceptor.process(request, request.getEntity(), context); assertEquals(HttpHeaders.VIA, request.getHeader(HttpHeaders.VIA).getName()); assertNotNull(request.getHeader(HttpHeaders.VIA)); assertEquals(request.getHeader(HttpHeaders.VIA).getValue(), viaValue); } }httpcore5/src/test/java/org/apache/hc/core5/http/ssl/0040755 0000000 0000000 00000000000 00000250600 021371 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/ssl/TLSTest.java0100644 0000000 0000000 00000010331 00000250600 023531 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.ssl; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.util.Tokenizer; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TLSTest { @Test void isSame() throws ParseException { assertTrue(TLS.V_1_0.isSame(TLS.parse("TLSv1"))); } @Test void isComparable() throws ParseException { assertTrue(TLS.V_1_0.isComparable(TLS.parse("TLSv1"))); } @Test void greaterEquals() throws ParseException { assertTrue(TLS.V_1_3.greaterEquals(TLS.parse("TLSv1"))); } @Test void lessEquals() throws ParseException { assertTrue(TLS.V_1_0.lessEquals(TLS.parse("TLSv1.3"))); } @Test void parse() throws ParseException { assertTrue(TLS.V_1_0.lessEquals(TLS.parse("TLSv1.3"))); } @Test void parseNull() throws ParseException { assertNull(TLS.parse(null)); } @Test void excludeWeakNull() { assertNull(TLS.excludeWeak((String[]) null)); } @Test void excludeWeak() { final String[] mixProtocol = { "SSL 2.0", "TLS 1.3", "SSL 3.0", "TLS 1.2", "TLS 1.1" }; final String[] strongProtocols = TLS.excludeWeak(mixProtocol); for (final String protocol : strongProtocols) { Assertions.assertTrue(TLS.isSecure(protocol)); } } @Test void testParseBasic() throws Exception { assertThat(TLS.parse("TLSv1"), CoreMatchers.equalTo(TLS.V_1_0.getVersion())); assertThat(TLS.parse("TLSv1.1"), CoreMatchers.equalTo(TLS.V_1_1.getVersion())); assertThat(TLS.parse("TLSv1.2"), CoreMatchers.equalTo(TLS.V_1_2.getVersion())); assertThat(TLS.parse("TLSv1.3 "), CoreMatchers.equalTo(TLS.V_1_3.getVersion())); assertThat(TLS.parse("TLSv22.356"), CoreMatchers.equalTo(new ProtocolVersion("TLS", 22, 356))); } @Test void testParseBuffer() throws Exception { final Tokenizer.Cursor cursor = new Tokenizer.Cursor(1, 13); assertThat(TLS.parse(" TLSv1.2,0000", cursor, Tokenizer.delimiters(',')), CoreMatchers.equalTo(TLS.V_1_2.getVersion())); assertThat(cursor.getPos(), CoreMatchers.equalTo(8)); } @Test void testParseFailure() { Assertions.assertThrows(ParseException.class, () -> TLS.parse("Tlsv1")); Assertions.assertThrows(ParseException.class, () -> TLS.parse("TLSV1")); Assertions.assertThrows(ParseException.class, () -> TLS.parse("TLSv")); Assertions.assertThrows(ParseException.class, () -> TLS.parse("TLSv1A")); Assertions.assertThrows(ParseException.class, () -> TLS.parse("TLSv1.A")); Assertions.assertThrows(ParseException.class, () -> TLS.parse("TLSv1.1 huh?")); } } httpcore5/src/test/java/org/apache/hc/core5/http/ssl/TestTlsCiphers.java0100644 0000000 0000000 00000011707 00000250600 025157 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.ssl; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link TlsCiphers}. */ class TestTlsCiphers { @Test void testStrongCipherSuites() { final String[] strongCipherSuites = { "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_RSA_WITH_AES_256_CBC_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_GCM_SHA384" }; for (final String cipherSuite : strongCipherSuites) { Assertions.assertFalse(TlsCiphers.isWeak(cipherSuite)); } } @Test void testWeakCiphersDisabledByDefault() { final String[] weakCiphersSuites = { "SSL_RSA_WITH_RC4_128_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_DH_anon_WITH_AES_128_CBC_SHA", "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_RSA_WITH_NULL_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", "TLS_DH_anon_WITH_AES_256_GCM_SHA384", "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_NULL_SHA256", "SSL_RSA_EXPORT_WITH_RC4_40_MD5", "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", "TLS_KRB5_EXPORT_WITH_RC4_40_SHA", "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5" }; for (final String cipherSuite : weakCiphersSuites) { Assertions.assertTrue(TlsCiphers.isWeak(cipherSuite)); } } @Test void excludeH2Blacklisted () { final String[] mixCipherSuites = { "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_RSA_WITH_AES_256_CBC_SHA256", "AES_SHA_US", "TLS_RSA_WITH_AES_128_CBC_SHA", "NULL_SHA", "TLS_RSA_WITH_AES_256_GCM_SHA384" }; final String[] strongCipherSuites = TlsCiphers.excludeH2Blacklisted(mixCipherSuites); for (final String cipherSuite : strongCipherSuites) { Assertions.assertFalse(TlsCiphers.isWeak(cipherSuite)); } } @Test void excludeWeak () { final String[] weakCiphersSuites = { "SSL_RSA_WITH_RC4_128_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_DH_anon_WITH_AES_128_CBC_SHA", "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_RSA_WITH_NULL_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", "TLS_DH_anon_WITH_AES_256_GCM_SHA384", "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_NULL_SHA256", "SSL_RSA_EXPORT_WITH_RC4_40_MD5", "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", "TLS_KRB5_EXPORT_WITH_RC4_40_SHA", "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_RSA_WITH_AES_256_CBC_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_GCM_SHA384" }; final String[] strongCipherSuites = TlsCiphers.excludeWeak(weakCiphersSuites); for (final String cipherSuite : strongCipherSuites) { Assertions.assertFalse(TlsCiphers.isWeak(cipherSuite)); } } @Test void excludeWeakNull() { Assertions.assertNull(TlsCiphers.excludeWeak((String[]) null)); } } httpcore5/src/test/java/org/apache/hc/core5/http/support/0040755 0000000 0000000 00000000000 00000250600 022304 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/http/support/TestBasicMessageBuilders.java0100644 0000000 0000000 00000030046 00000250600 030027 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.support; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import org.apache.hc.core5.http.HeaderMatcher; import org.apache.hc.core5.http.HeadersMatcher; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.message.BasicNameValuePair; import org.apache.hc.core5.net.URIAuthority; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Arrays; /** * Simple tests for {@link BasicResponseBuilder} and {@link BasicRequestBuilder}. */ class TestBasicMessageBuilders { @Test void testResponseBasics() { final BasicResponseBuilder builder = BasicResponseBuilder.create(200); Assertions.assertEquals(200, builder.getStatus()); Assertions.assertNull(builder.getHeaders()); Assertions.assertNull(builder.getVersion()); final BasicHttpResponse r1 = builder.build(); Assertions.assertNotNull(r1); Assertions.assertEquals(200, r1.getCode()); Assertions.assertNull(r1.getVersion()); builder.setStatus(500); builder.setVersion(HttpVersion.HTTP_1_0); Assertions.assertEquals(500, builder.getStatus()); Assertions.assertEquals(HttpVersion.HTTP_1_0, builder.getVersion()); final BasicHttpResponse r2 = builder.build(); Assertions.assertEquals(500, r2.getCode()); Assertions.assertEquals(HttpVersion.HTTP_1_0, r2.getVersion()); builder.addHeader("h1", "v1"); builder.addHeader("h1", "v2"); builder.addHeader("h2", "v2"); assertThat(builder.getHeaders(), HeadersMatcher.same( new BasicHeader("h1", "v1"), new BasicHeader("h1", "v2"), new BasicHeader("h2", "v2"))); assertThat(builder.getHeaders("h1"), HeadersMatcher.same( new BasicHeader("h1", "v1"), new BasicHeader("h1", "v2"))); assertThat(builder.getFirstHeader("h1"), HeaderMatcher.same("h1", "v1")); assertThat(builder.getLastHeader("h1"), HeaderMatcher.same("h1", "v2")); final BasicHttpResponse r3 = builder.build(); assertThat(r3.getHeaders(), HeadersMatcher.same( new BasicHeader("h1", "v1"), new BasicHeader("h1", "v2"), new BasicHeader("h2", "v2"))); assertThat(r3.getHeaders("h1"), HeadersMatcher.same( new BasicHeader("h1", "v1"), new BasicHeader("h1", "v2"))); assertThat(r3.getFirstHeader("h1"), HeaderMatcher.same("h1", "v1")); assertThat(r3.getLastHeader("h1"), HeaderMatcher.same("h1", "v2")); builder.removeHeader(new BasicHeader("h1", "v2")); assertThat(builder.getHeaders("h1"), HeadersMatcher.same(new BasicHeader("h1", "v1"))); assertThat(builder.getHeaders(), HeadersMatcher.same( new BasicHeader("h1", "v1"), new BasicHeader("h2", "v2"))); final BasicHttpResponse r4 = builder.build(); assertThat(r4.getHeaders("h1"), HeadersMatcher.same(new BasicHeader("h1", "v1"))); assertThat(r4.getHeaders(), HeadersMatcher.same( new BasicHeader("h1", "v1"), new BasicHeader("h2", "v2"))); builder.removeHeaders("h1"); assertThat(builder.getHeaders("h1"), HeadersMatcher.same()); assertThat(builder.getHeaders(), HeadersMatcher.same(new BasicHeader("h2", "v2"))); final BasicHttpResponse r5 = builder.build(); assertThat(r5.getHeaders("h1"), HeadersMatcher.same()); assertThat(r5.getHeaders(), HeadersMatcher.same(new BasicHeader("h2", "v2"))); } @Test void testRequestBasics() throws Exception { final BasicRequestBuilder builder = BasicRequestBuilder.get(); Assertions.assertEquals(URI.create("/"), builder.getUri()); Assertions.assertEquals("GET", builder.getMethod()); Assertions.assertNull(builder.getScheme()); Assertions.assertNull(builder.getAuthority()); Assertions.assertNull(builder.getPath()); Assertions.assertNull(builder.getHeaders()); Assertions.assertNull(builder.getVersion()); Assertions.assertNull(builder.getCharset()); Assertions.assertNull(builder.getParameters()); final BasicHttpRequest r1 = builder.build(); Assertions.assertNotNull(r1); Assertions.assertEquals("GET", r1.getMethod()); Assertions.assertNull(r1.getScheme()); Assertions.assertNull(r1.getAuthority()); Assertions.assertNull(r1.getPath()); Assertions.assertEquals(URI.create("/"), r1.getUri()); Assertions.assertNull(r1.getVersion()); builder.setUri(URI.create("http://host:1234/blah?param=value")); builder.setVersion(HttpVersion.HTTP_1_1); Assertions.assertEquals("http", builder.getScheme()); Assertions.assertEquals(new URIAuthority("host", 1234), builder.getAuthority()); Assertions.assertEquals("/blah?param=value", builder.getPath()); Assertions.assertEquals(URI.create("http://host:1234/blah?param=value"), builder.getUri()); Assertions.assertEquals(HttpVersion.HTTP_1_1, builder.getVersion()); final BasicHttpRequest r2 = builder.build(); Assertions.assertEquals("GET", r2.getMethod()); Assertions.assertEquals("http", r2.getScheme()); Assertions.assertEquals(new URIAuthority("host", 1234), r2.getAuthority()); Assertions.assertEquals("/blah?param=value", r2.getPath()); Assertions.assertEquals(URI.create("http://host:1234/blah?param=value"), r2.getUri()); Assertions.assertEquals(HttpVersion.HTTP_1_1, builder.getVersion()); builder.setCharset(StandardCharsets.US_ASCII); builder.addParameter("param1", "value1"); builder.addParameter("param2", null); builder.addParameters(new BasicNameValuePair("param3", "value3"), new BasicNameValuePair("param4", null)); Assertions.assertEquals(builder.getParameters(), Arrays.asList( new BasicNameValuePair("param1", "value1"), new BasicNameValuePair("param2", null), new BasicNameValuePair("param3", "value3"), new BasicNameValuePair("param4", null) )); Assertions.assertEquals(URI.create("http://host:1234/blah?param=value"), builder.getUri()); final BasicHttpRequest r3 = builder.build(); Assertions.assertEquals("GET", r3.getMethod()); Assertions.assertEquals("http", r3.getScheme()); Assertions.assertEquals(new URIAuthority("host", 1234), r3.getAuthority()); Assertions.assertEquals("/blah?param=value¶m1=value1¶m2¶m3=value3¶m4", r3.getPath()); Assertions.assertEquals(URI.create("http://host:1234/blah?param=value¶m1=value1¶m2¶m3=value3¶m4"), r3.getUri()); builder.addHeader("h1", "v1"); builder.addHeader("h1", "v2"); builder.addHeader("h2", "v2"); assertThat(builder.getHeaders(), HeadersMatcher.same( new BasicHeader("h1", "v1"), new BasicHeader("h1", "v2"), new BasicHeader("h2", "v2"))); assertThat(builder.getHeaders("h1"), HeadersMatcher.same( new BasicHeader("h1", "v1"), new BasicHeader("h1", "v2"))); assertThat(builder.getFirstHeader("h1"), HeaderMatcher.same("h1", "v1")); assertThat(builder.getLastHeader("h1"), HeaderMatcher.same("h1", "v2")); final BasicHttpRequest r4 = builder.build(); assertThat(r4.getHeaders(), HeadersMatcher.same( new BasicHeader("h1", "v1"), new BasicHeader("h1", "v2"), new BasicHeader("h2", "v2"))); assertThat(r4.getHeaders("h1"), HeadersMatcher.same( new BasicHeader("h1", "v1"), new BasicHeader("h1", "v2"))); assertThat(r4.getFirstHeader("h1"), HeaderMatcher.same("h1", "v1")); assertThat(r4.getLastHeader("h1"), HeaderMatcher.same("h1", "v2")); builder.removeHeader(new BasicHeader("h1", "v2")); assertThat(builder.getHeaders("h1"), HeadersMatcher.same(new BasicHeader("h1", "v1"))); assertThat(builder.getHeaders(), HeadersMatcher.same( new BasicHeader("h1", "v1"), new BasicHeader("h2", "v2"))); final BasicHttpRequest r5 = builder.build(); assertThat(r5.getHeaders("h1"), HeadersMatcher.same(new BasicHeader("h1", "v1"))); assertThat(r5.getHeaders(), HeadersMatcher.same( new BasicHeader("h1", "v1"), new BasicHeader("h2", "v2"))); builder.removeHeaders("h1"); assertThat(builder.getHeaders("h1"), HeadersMatcher.same()); assertThat(builder.getHeaders(), HeadersMatcher.same(new BasicHeader("h2", "v2"))); final BasicHttpRequest r6 = builder.build(); assertThat(r6.getHeaders("h1"), HeadersMatcher.same()); assertThat(r6.getHeaders(), HeadersMatcher.same(new BasicHeader("h2", "v2"))); } @Test void testResponseCopy() { final HttpResponse response = new BasicHttpResponse(400); response.addHeader("h1", "v1"); response.addHeader("h1", "v2"); response.addHeader("h2", "v2"); response.setVersion(HttpVersion.HTTP_2); final BasicResponseBuilder builder = BasicResponseBuilder.copy(response); Assertions.assertEquals(400, builder.getStatus()); Assertions.assertEquals(HttpVersion.HTTP_2, builder.getVersion()); assertThat(builder.getHeaders(), HeadersMatcher.same( new BasicHeader("h1", "v1"), new BasicHeader("h1", "v2"), new BasicHeader("h2", "v2"))); } @Test void testRequestCopy() { final HttpRequest request = new BasicHttpRequest(Method.GET, URI.create("https://host:3456/stuff?blah")) ; request.addHeader("h1", "v1"); request.addHeader("h1", "v2"); request.addHeader("h2", "v2"); request.setVersion(HttpVersion.HTTP_2); final BasicRequestBuilder builder = BasicRequestBuilder.copy(request); Assertions.assertEquals("GET", builder.getMethod()); Assertions.assertEquals("https", builder.getScheme()); Assertions.assertEquals(new URIAuthority("host", 3456), builder.getAuthority()); Assertions.assertEquals("/stuff?blah", builder.getPath()); Assertions.assertEquals(HttpVersion.HTTP_2, builder.getVersion()); assertThat(builder.getHeaders(), HeadersMatcher.same( new BasicHeader("h1", "v1"), new BasicHeader("h1", "v2"), new BasicHeader("h2", "v2"))); } @Test void testIDNIntegration() { final String url = "http://müller.example.com:8080/path"; final HttpRequest request = new BasicHttpRequest(Method.GET, URI.create(url)); assertEquals(new URIAuthority("xn--mller-kva.example.com",8080), request.getAuthority()); } } httpcore5/src/test/java/org/apache/hc/core5/http/support/TestExpectSupport.java0100644 0000000 0000000 00000010513 00000250600 026631 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http.support; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.impl.BasicEntityDetails; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestExpectSupport { @Test void testExpectParsingBasics() throws Exception { Assertions.assertEquals(Expectation.CONTINUE, ExpectSupport.parse( BasicRequestBuilder.post() .addHeader("Expect", "100-continue") .build(), new BasicEntityDetails(100, ContentType.TEXT_PLAIN))); } @Test void testExpectParsingTolerateEmptyTokens() throws Exception { Assertions.assertEquals(Expectation.CONTINUE, ExpectSupport.parse( BasicRequestBuilder.post() .addHeader("Expect", ",,,") .addHeader("Expect", ",100-continue") .addHeader("Expect", ",,,") .build(), new BasicEntityDetails(100, ContentType.TEXT_PLAIN))); } @Test void testExpectParsingMissingEntity() { Assertions.assertThrows(ProtocolException.class, () -> ExpectSupport.parse( BasicRequestBuilder.post() .addHeader("Expect", "100-continue") .build(), null)); } @Test void testExpectParsingUnknownExpectation() throws Exception { Assertions.assertEquals(Expectation.UNKNOWN, ExpectSupport.parse( BasicRequestBuilder.post() .addHeader("Expect", "whatever") .addHeader("Expect", "100-continue") .build(), new BasicEntityDetails(100, ContentType.TEXT_PLAIN))); } @Test void testExpectParsingUnknownExpectation2() throws Exception { Assertions.assertEquals(Expectation.UNKNOWN, ExpectSupport.parse( BasicRequestBuilder.post() .addHeader("Expect", "100-continue, whatever") .build(), new BasicEntityDetails(100, ContentType.TEXT_PLAIN))); } @Test void testExpectParsingNoExpectation() throws Exception { Assertions.assertNull(ExpectSupport.parse( BasicRequestBuilder.post() .build(), new BasicEntityDetails(100, ContentType.TEXT_PLAIN))); } @Test void testExpectParsingIgnoreHTTP10() throws Exception { Assertions.assertNull(ExpectSupport.parse( BasicRequestBuilder.post() .setVersion(HttpVersion.HTTP_1_0) .addHeader("Expect", "100-continue") .build(), new BasicEntityDetails(100, ContentType.TEXT_PLAIN))); } } httpcore5/src/test/java/org/apache/hc/core5/net/0040755 0000000 0000000 00000000000 00000250600 020377 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/net/TestHost.java0100644 0000000 0000000 00000013602 00000250600 023016 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.net; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.URISyntaxException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link Host}. * */ class TestHost { @Test void testConstructor() { final Host host1 = new Host("somehost", 8080); Assertions.assertEquals("somehost", host1.getHostName()); Assertions.assertEquals(8080, host1.getPort()); final Host host2 = new Host("somehost", 0); Assertions.assertEquals("somehost", host2.getHostName()); Assertions.assertEquals(0, host2.getPort()); final Host host3 = new Host("Яндекс.Ру", 0); Assertions.assertEquals("Яндекс.Ру", host3.getHostName()); Assertions.assertEquals(0, host3.getPort()); final Host host4 = new Host("xn--d1acpjx3f.xn--p1ag", 0); Assertions.assertEquals("яндекс.ру", host4.getHostName()); Assertions.assertEquals(0, host4.getPort()); final Host host5 = new Host("XN--D1Acpjx3f.xn--p1ag", 0); Assertions.assertEquals("яндекс.ру", host5.getHostName()); Assertions.assertEquals(0, host5.getPort()); Assertions.assertThrows(NullPointerException.class, () -> new Host(null, 0)); } @Test void testHashCode() { final Host host1 = new Host("somehost", 8080); final Host host2 = new Host("somehost", 80); final Host host3 = new Host("someotherhost", 8080); final Host host4 = new Host("somehost", 80); Assertions.assertEquals(host1.hashCode(), host1.hashCode()); Assertions.assertNotEquals(host1.hashCode(), host2.hashCode()); Assertions.assertNotEquals(host1.hashCode(), host3.hashCode()); Assertions.assertEquals(host2.hashCode(), host4.hashCode()); } @Test void testEquals() { final Host host1 = new Host("somehost", 8080); final Host host2 = new Host("somehost", 80); final Host host3 = new Host("someotherhost", 8080); final Host host4 = new Host("somehost", 80); Assertions.assertEquals(host1, host1); Assertions.assertNotEquals(host1, host2); Assertions.assertNotEquals(host1, host3); Assertions.assertEquals(host2, host4); } @Test void testToString() { final Host host1 = new Host("somehost", 8888); Assertions.assertEquals("somehost:8888", host1.toString()); } @Test void testSerialization() throws Exception { final Host orig = new Host("somehost", 8080); final ByteArrayOutputStream outbuffer = new ByteArrayOutputStream(); try (ObjectOutputStream outStream = new ObjectOutputStream(outbuffer)) { outStream.writeObject(orig); } final byte[] raw = outbuffer.toByteArray(); final ByteArrayInputStream inBuffer = new ByteArrayInputStream(raw); final ObjectInputStream inStream = new ObjectInputStream(inBuffer); final Host clone = (Host) inStream.readObject(); Assertions.assertEquals(orig, clone); } @Test void testCreateFromString() throws Exception { Assertions.assertEquals(new Host("somehost", 8080), Host.create("somehost:8080")); Assertions.assertEquals(new Host("somehost", 1234), Host.create("somehost:1234")); Assertions.assertEquals(new Host("somehost", 0), Host.create("somehost:0")); Assertions.assertEquals(new Host("яндекс.ру", -1), Host.create("xn--d1acpjx3f.xn--p1ag")); Assertions.assertEquals(new Host("Яндекс.Ру", -1), Host.create("Яндекс.Ру")); } @Test void testCreateFromStringInvalid() { Assertions.assertThrows(URISyntaxException.class, () -> Host.create(" host ")); Assertions.assertThrows(URISyntaxException.class, () -> Host.create("host :8080")); Assertions.assertThrows(IllegalArgumentException.class, () -> Host.create("")); } @Test void testIpv6HostAndPort() throws Exception { final Host host = Host.create("[::1]:80"); Assertions.assertEquals("::1", host.getHostName()); Assertions.assertEquals(80, host.getPort()); } @Test void testIpv6HostAndPortWithoutBrackets() { // ambiguous Assertions.assertThrows(URISyntaxException.class, () -> Host.create("::1:80")); } @Test void testIpv6HostWithoutPort() { Assertions.assertThrows(URISyntaxException.class, () -> Host.create("::1")); } @Test void testIpv6HostToString() { Assertions.assertEquals("[::1]:80", new Host("::1", 80).toString()); Assertions.assertEquals("[::1]", new Host("::1", -1).toString()); } } httpcore5/src/test/java/org/apache/hc/core5/net/TestInetAddressUtils.java0100644 0000000 0000000 00000030557 00000250600 025337 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.net; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for InetAddressUtils. */ class TestInetAddressUtils { @Test void testValidIPv4Address() { Assertions.assertTrue(InetAddressUtils.isIPv4("127.0.0.1")); Assertions.assertTrue(InetAddressUtils.isIPv4("192.168.0.0")); Assertions.assertTrue(InetAddressUtils.isIPv4("255.255.255.255")); Assertions.assertTrue(InetAddressUtils.isIPv4("0.168.0.0")); } @Test void testInvalidIPv4Address() { Assertions.assertFalse(InetAddressUtils.isIPv4(" 127.0.0.1 ")); // Blanks not allowed Assertions.assertFalse(InetAddressUtils.isIPv4("g.ar.ba.ge")); Assertions.assertFalse(InetAddressUtils.isIPv4("192.168.0")); Assertions.assertFalse(InetAddressUtils.isIPv4("256.255.255.255")); } @Test void testValidIPv6Address() { Assertions.assertTrue(InetAddressUtils.isIPv6Std("2001:0db8:0000:0000:0000:0000:1428:57ab")); Assertions.assertTrue(InetAddressUtils.isIPv6Std("2001:db8:0:0:0:0:1428:57ab")); Assertions.assertTrue(InetAddressUtils.isIPv6Std("0:0:0:0:0:0:0:0")); Assertions.assertTrue(InetAddressUtils.isIPv6Std("0:0:0:0:0:0:0:1")); Assertions.assertTrue(InetAddressUtils.isIPv6HexCompressed("2001:0db8:0:0::1428:57ab")); Assertions.assertTrue(InetAddressUtils.isIPv6HexCompressed("2001:0db8::1428:57ab")); Assertions.assertTrue(InetAddressUtils.isIPv6HexCompressed("2001:db8::1428:57ab")); Assertions.assertTrue(InetAddressUtils.isIPv6HexCompressed("::1")); Assertions.assertTrue(InetAddressUtils.isIPv6HexCompressed("::")); // http://tools.ietf.org/html/rfc4291#section-2.2 Assertions.assertTrue(InetAddressUtils.isIPv6("2001:0db8:0000:0000:0000:0000:1428:57ab")); Assertions.assertTrue(InetAddressUtils.isIPv6("2001:db8:0:0:0:0:1428:57ab")); Assertions.assertTrue(InetAddressUtils.isIPv6("0:0:0:0:0:0:0:0")); Assertions.assertTrue(InetAddressUtils.isIPv6("0:0:0:0:0:0:0:1")); Assertions.assertTrue(InetAddressUtils.isIPv6("2001:0db8:0:0::1428:57ab")); Assertions.assertTrue(InetAddressUtils.isIPv6("2001:0db8::1428:57ab")); Assertions.assertTrue(InetAddressUtils.isIPv6("2001:db8::1428:57ab")); Assertions.assertTrue(InetAddressUtils.isIPv6("::1")); Assertions.assertTrue(InetAddressUtils.isIPv6("::")); // http://tools.ietf.org/html/rfc4291#section-2.2 //HTTPCORE-674 InetAddressUtils scoped ID support Assertions.assertTrue(InetAddressUtils.isIPv6("fe80::1ff:fe23:4567:890a")); Assertions.assertTrue(InetAddressUtils.isIPv6("fe80::1ff:fe23:4567:890a%eth2")); Assertions.assertTrue(InetAddressUtils.isIPv6("fe80::1ff:fe23:4567:890a%3")); } @Test void testInvalidIPv6Address() { Assertions.assertFalse(InetAddressUtils.isIPv6("2001:0db8:0000:garb:age0:0000:1428:57ab")); Assertions.assertFalse(InetAddressUtils.isIPv6("2001:0gb8:0000:0000:0000:0000:1428:57ab")); Assertions.assertFalse(InetAddressUtils.isIPv6Std("0:0:0:0:0:0:0:0:0")); // Too many Assertions.assertFalse(InetAddressUtils.isIPv6Std("0:0:0:0:0:0:0")); // Too few Assertions.assertFalse(InetAddressUtils.isIPv6HexCompressed(":1")); Assertions.assertFalse(InetAddressUtils.isIPv6(":1")); Assertions.assertFalse(InetAddressUtils.isIPv6("2001:0db8::0000::57ab")); // Cannot have two contractions Assertions.assertFalse(InetAddressUtils.isIPv6HexCompressed("1:2:3:4:5:6:7::9")); // too many fields before :: Assertions.assertFalse(InetAddressUtils.isIPv6HexCompressed("1::3:4:5:6:7:8:9")); // too many fields after :: Assertions.assertFalse(InetAddressUtils.isIPv6HexCompressed("::3:4:5:6:7:8:9")); // too many fields after :: Assertions.assertFalse(InetAddressUtils.isIPv6("")); // empty //Invalid scoped IDs Assertions.assertFalse(InetAddressUtils.isIPv6("fe80::1ff:fe23:4567:890a%eth2#")); Assertions.assertFalse(InetAddressUtils.isIPv6("fe80::1ff:fe23:4567:890a%3@")); Assertions.assertFalse(InetAddressUtils.isIPv6("fe80::1ff:fe23:4567:890a#eth2")); Assertions.assertFalse(InetAddressUtils.isIPv6("fe80::1ff:fe23:4567:890a%")); Assertions.assertFalse(InetAddressUtils.isIPv6("fe80::1ff:fe23:4567:890a%eth2!")); Assertions.assertFalse(InetAddressUtils.isIPv6("2001:0db8:0:0::1428:57ab%")); Assertions.assertFalse(InetAddressUtils.isIPv6("2001:0db8:0:0::1428:57ab%eth2#")); Assertions.assertFalse(InetAddressUtils.isIPv6("fe80::1ff:fe23:4567:890a%eth2#3")); Assertions.assertFalse(InetAddressUtils.isIPv6("2001:0db8:0:0::1428:57ab%eth2#3")); Assertions.assertFalse(InetAddressUtils.isIPv6("fe80::1ff:fe23:4567:890a%3#eth2")); Assertions.assertFalse(InetAddressUtils.isIPv6("2001:0db8:0:0::1428:57ab%3#eth2")); } @Test void testValidIPv6BracketAddress() { Assertions.assertTrue(InetAddressUtils.isIPv6URLBracketed("[2001:0db8:0000:0000:0000:0000:1428:57ab]")); Assertions.assertTrue(InetAddressUtils.isIPv6URLBracketed("[2001:db8:0:0:0:0:1428:57ab]")); Assertions.assertTrue(InetAddressUtils.isIPv6URLBracketed("[0:0:0:0:0:0:0:0]")); Assertions.assertTrue(InetAddressUtils.isIPv6URLBracketed("[0:0:0:0:0:0:0:1]")); Assertions.assertTrue(InetAddressUtils.isIPv6URLBracketed("[2001:0db8:0:0::1428:57ab]")); Assertions.assertTrue(InetAddressUtils.isIPv6URLBracketed("[2001:0db8::1428:57ab]")); Assertions.assertTrue(InetAddressUtils.isIPv6URLBracketed("[2001:db8::1428:57ab]")); Assertions.assertTrue(InetAddressUtils.isIPv6URLBracketed("[::1]")); // http://tools.ietf.org/html/rfc4291#section-2.2 Assertions.assertTrue(InetAddressUtils.isIPv6URLBracketed("[::]")); } @Test void testInvalidIPv6BracketAddress() { Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("2001:0db8:0000:garb:age0:0000:1428:57ab")); Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("[2001:0db8:0000:garb:age0:0000:1428:57ab]")); Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("2001:0gb8:0000:0000:0000:0000:1428:57ab")); Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("[2001:0gb8:0000:0000:0000:0000:1428:57ab]")); // Too many Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("0:0:0:0:0:0:0:0:0")); Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("[0:0:0:0:0:0:0:0:0]")); // Too few Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("0:0:0:0:0:0:0")); Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("[0:0:0:0:0:0:0]")); Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed(":1")); Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("[:1]")); // Cannot have two contractions Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("2001:0db8::0000::57ab")); Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("[2001:0db8::0000::57ab]")); // too many fields before :: Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("1:2:3:4:5:6:7::9")); Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("[1:2:3:4:5:6:7::9]")); // too many fields after :: Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("1::3:4:5:6:7:8:9")); Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("[1::3:4:5:6:7:8:9]")); // too many fields after :: Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("::3:4:5:6:7:8:9")); Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("[::3:4:5:6:7:8:9]")); // empty Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("")); Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("[]")); // missing brackets Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("::")); Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("::1")); Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("2001:db8::1428:57ab")); } @Test // Test HTTPCLIENT-1319 void testInvalidIPv6AddressIncorrectGroupCount() { Assertions.assertFalse(InetAddressUtils.isIPv6HexCompressed("1:2::4:5:6:7:8:9")); // too many fields in total Assertions.assertFalse(InetAddressUtils.isIPv6HexCompressed("1:2:3:4:5:6::8:9")); // too many fields in total } @Test void testHasValidIPv6ColonCount() { Assertions.assertFalse(InetAddressUtils.hasValidIPv6ColonCount("")); Assertions.assertFalse(InetAddressUtils.hasValidIPv6ColonCount(":")); Assertions.assertFalse(InetAddressUtils.hasValidIPv6ColonCount("127.0.0.1")); Assertions.assertFalse(InetAddressUtils.hasValidIPv6ColonCount(":0")); Assertions.assertFalse(InetAddressUtils.hasValidIPv6ColonCount("0:")); Assertions.assertFalse(InetAddressUtils.hasValidIPv6ColonCount("1:2:3:4:5:6:7:8:")); Assertions.assertFalse(InetAddressUtils.hasValidIPv6ColonCount("1:2:3:4:5:6:7:8:9")); Assertions.assertTrue(InetAddressUtils.hasValidIPv6ColonCount("2001:0db8:0000:0000:0000:0000:1428:57ab")); Assertions.assertTrue(InetAddressUtils.hasValidIPv6ColonCount("2001:db8:0:0:0:0:1428:57ab")); Assertions.assertTrue(InetAddressUtils.hasValidIPv6ColonCount("0:0:0:0:0:0:0:0")); Assertions.assertTrue(InetAddressUtils.hasValidIPv6ColonCount("0:0:0:0:0:0:0:1")); Assertions.assertTrue(InetAddressUtils.hasValidIPv6ColonCount("2001:0db8:0:0::1428:57ab")); Assertions.assertTrue(InetAddressUtils.hasValidIPv6ColonCount("2001:0db8::1428:57ab")); Assertions.assertTrue(InetAddressUtils.hasValidIPv6ColonCount("2001:db8::1428:57ab")); Assertions.assertTrue(InetAddressUtils.hasValidIPv6ColonCount("::1")); Assertions.assertTrue(InetAddressUtils.hasValidIPv6ColonCount("::")); // http://tools.ietf.org/html/rfc4291#section-2.2 } @Test void testValidIPv4MappedIPv6Address() { Assertions.assertTrue(InetAddressUtils.isIPv4MappedIPv6("::FFFF:1.2.3.4")); Assertions.assertTrue(InetAddressUtils.isIPv4MappedIPv6("::ffff:255.255.255.255")); } @Test void testInValidIPv4MappedIPv6Address() { Assertions.assertFalse(InetAddressUtils.isIPv4MappedIPv6("2001:0db8:0000:0000:0000:0000:1428:57ab")); Assertions.assertFalse(InetAddressUtils.isIPv4MappedIPv6("::ffff:1:2:3:4")); } @Test void testValidIPv4MappedIPv6AddressWithLeadingZeros() { Assertions.assertTrue(InetAddressUtils.isIPv4MappedIPv6("::ffff:001.002.003.004")); Assertions.assertTrue(InetAddressUtils.isIPv4MappedIPv6("::FFFF:000.000.000.255")); Assertions.assertTrue(InetAddressUtils.isIPv4MappedIPv6("::ffff:010.020.030.040")); } @Test void testInvalidIPv4MappedIPv6AddressWithBadOctets() { // >255 not allowed Assertions.assertFalse(InetAddressUtils.isIPv4MappedIPv6("::ffff:256.000.000.000")); // too few octets Assertions.assertFalse(InetAddressUtils.isIPv4MappedIPv6("::ffff:01.02.03")); // too many digits in an octet (4 digits) Assertions.assertFalse(InetAddressUtils.isIPv4MappedIPv6("::ffff:0255.000.000.000")); } } httpcore5/src/test/java/org/apache/hc/core5/net/TestPercentCodec.java0100644 0000000 0000000 00000010177 00000250600 024443 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.net; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import java.nio.charset.StandardCharsets; import java.util.stream.Stream; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; /** * Unit tests for {@link PercentCodec}. */ class TestPercentCodec { @Test void testCoding() { final StringBuilder buf = new StringBuilder(); PercentCodec.encode(buf, "blah!", StandardCharsets.UTF_8); PercentCodec.encode(buf, " ~ ", StandardCharsets.UTF_8); PercentCodec.encode(buf, "huh?", StandardCharsets.UTF_8); assertThat(buf.toString(), CoreMatchers.equalTo("blah%21%20~%20huh%3F")); } @Test void testDecoding() { assertThat(PercentCodec.decode("blah%21%20~%20huh%3F", StandardCharsets.UTF_8), CoreMatchers.equalTo("blah! ~ huh?")); assertThat(PercentCodec.decode("blah%21+~%20huh%3F", StandardCharsets.UTF_8), CoreMatchers.equalTo("blah!+~ huh?")); assertThat(PercentCodec.decode("blah%21+~%20huh%3F", StandardCharsets.UTF_8, true), CoreMatchers.equalTo("blah! ~ huh?")); } @Test void testDecodingPartialContent() { assertThat(PercentCodec.decode("blah%21%20%", StandardCharsets.UTF_8), CoreMatchers.equalTo("blah! %")); assertThat(PercentCodec.decode("blah%21%20%a", StandardCharsets.UTF_8), CoreMatchers.equalTo("blah! %a")); assertThat(PercentCodec.decode("blah%21%20%wa", StandardCharsets.UTF_8), CoreMatchers.equalTo("blah! %wa")); } @ParameterizedTest @MethodSource("params") void testRfc5987EncodingDecoding(final String input, final String expected) { assertEquals(expected, PercentCodec.RFC5987.encode(input)); assertEquals(input, PercentCodec.RFC5987.decode(expected)); } static Stream params() { return Stream.of( new Object[]{"foo-ä-€.html", "foo-%C3%A4-%E2%82%AC.html"}, new Object[]{"世界ーファイル 2.jpg", "%E4%B8%96%E7%95%8C%E3%83%BC%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%202.jpg"}, new Object[]{"foo.jpg", "foo.jpg"}, new Object[]{"simple", "simple"}, // Unreserved characters new Object[]{"reserved/chars?", "reserved%2Fchars%3F"}, // Reserved characters new Object[]{"", ""}, // Empty string new Object[]{"space test", "space%20test"}, // String with space new Object[]{"ümlaut", "%C3%BCmlaut"} // Non-ASCII characters ); } @Test void verifyRfc5987EncodingandDecoding() { final String s = "!\"$£%^&*()_-+={[}]:@~;'#,./<>?\\|✓éèæðŃœ"; assertThat(PercentCodec.RFC5987.decode(PercentCodec.RFC5987.encode(s)), CoreMatchers.equalTo(s)); } } httpcore5/src/test/java/org/apache/hc/core5/net/TestURIAuthority.java0100644 0000000 0000000 00000025671 00000250600 024462 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.net; import static org.hamcrest.MatcherAssert.assertThat; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.URISyntaxException; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link URIAuthority}. * */ class TestURIAuthority { @Test void testConstructor() { final URIAuthority host1 = new URIAuthority("somehost"); Assertions.assertEquals("somehost", host1.getHostName()); Assertions.assertEquals(-1, host1.getPort()); final URIAuthority host2 = new URIAuthority("somehost", 8080); Assertions.assertEquals("somehost", host2.getHostName()); Assertions.assertEquals(8080, host2.getPort()); final URIAuthority host3 = new URIAuthority("somehost", -1); Assertions.assertEquals("somehost", host3.getHostName()); Assertions.assertEquals(-1, host3.getPort()); } @Test void testHashCode() { final URIAuthority host1 = new URIAuthority("somehost", 8080); final URIAuthority host2 = new URIAuthority("somehost", 80); final URIAuthority host3 = new URIAuthority("someotherhost", 8080); final URIAuthority host4 = new URIAuthority("somehost", 80); final URIAuthority host5 = new URIAuthority("SomeHost", 80); final URIAuthority host6 = new URIAuthority("user", "SomeHost", 80); final URIAuthority host7 = new URIAuthority("user", "somehost", 80); Assertions.assertEquals(host1.hashCode(), host1.hashCode()); Assertions.assertNotEquals(host1.hashCode(), host2.hashCode()); Assertions.assertNotEquals(host1.hashCode(), host3.hashCode()); Assertions.assertEquals(host2.hashCode(), host4.hashCode()); Assertions.assertEquals(host2.hashCode(), host5.hashCode()); Assertions.assertNotEquals(host5.hashCode(), host6.hashCode()); Assertions.assertEquals(host6.hashCode(), host7.hashCode()); } @Test void testEquals() { final URIAuthority host1 = new URIAuthority("somehost", 8080); final URIAuthority host2 = new URIAuthority("somehost", 80); final URIAuthority host3 = new URIAuthority("someotherhost", 8080); final URIAuthority host4 = new URIAuthority("somehost", 80); final URIAuthority host5 = new URIAuthority("SomeHost", 80); final URIAuthority host6 = new URIAuthority("user", "SomeHost", 80); final URIAuthority host7 = new URIAuthority("user", "somehost", 80); Assertions.assertEquals(host1, host1); Assertions.assertNotEquals(host1, host2); Assertions.assertNotEquals(host1, host3); Assertions.assertEquals(host2, host4); Assertions.assertEquals(host2, host5); Assertions.assertNotEquals(host5, host6); Assertions.assertEquals(host6, host7); } @Test void testToString() { final URIAuthority host1 = new URIAuthority("somehost"); Assertions.assertEquals("somehost", host1.toString()); final URIAuthority host2 = new URIAuthority("somehost", -1); Assertions.assertEquals("somehost", host2.toString()); final URIAuthority host3 = new URIAuthority("somehost", 8888); Assertions.assertEquals("somehost:8888", host3.toString()); } @Test void testSerialization() throws Exception { final URIAuthority orig = new URIAuthority("somehost", 8080); final ByteArrayOutputStream outbuffer = new ByteArrayOutputStream(); try (ObjectOutputStream outStream = new ObjectOutputStream(outbuffer)) { outStream.writeObject(orig); } final byte[] raw = outbuffer.toByteArray(); final ByteArrayInputStream inBuffer = new ByteArrayInputStream(raw); final ObjectInputStream inStream = new ObjectInputStream(inBuffer); final URIAuthority clone = (URIAuthority) inStream.readObject(); Assertions.assertEquals(orig, clone); } @Test void testParse() throws Exception { assertThat(URIAuthority.parse("somehost"), CoreMatchers.equalTo(new URIAuthority("somehost", -1))); assertThat(URIAuthority.parse("somehost/blah"), CoreMatchers.equalTo(new URIAuthority("somehost", -1))); assertThat(URIAuthority.parse("somehost?blah"), CoreMatchers.equalTo(new URIAuthority("somehost", -1))); assertThat(URIAuthority.parse("somehost#blah"), CoreMatchers.equalTo(new URIAuthority("somehost", -1))); assertThat(URIAuthority.parse("aaaa@:8080"), CoreMatchers.equalTo(new URIAuthority("aaaa", "", 8080))); assertThat(URIAuthority.parse("@:"), CoreMatchers.equalTo(new URIAuthority(null, "", -1))); assertThat(URIAuthority.parse("somehost:8080"), CoreMatchers.equalTo(new URIAuthority("somehost", 8080))); assertThat(URIAuthority.parse("somehost:8080/blah"), CoreMatchers.equalTo(new URIAuthority("somehost", 8080))); assertThat(URIAuthority.parse("somehost:8080?blah"), CoreMatchers.equalTo(new URIAuthority("somehost", 8080))); assertThat(URIAuthority.parse("somehost:8080#blah"), CoreMatchers.equalTo(new URIAuthority("somehost", 8080))); assertThat(URIAuthority.parse("somehost:008080"), CoreMatchers.equalTo(new URIAuthority("somehost", 8080))); Assertions.assertThrows(URISyntaxException.class, () -> URIAuthority.create("somehost:aaaaa")); Assertions.assertThrows(URISyntaxException.class, () -> URIAuthority.create("somehost:90ab")); assertThat(URIAuthority.parse("someuser@somehost"), CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", -1))); assertThat(URIAuthority.parse("someuser@somehost/blah"), CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", -1))); assertThat(URIAuthority.parse("someuser@somehost?blah"), CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", -1))); assertThat(URIAuthority.parse("someuser@somehost#blah"), CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", -1))); assertThat(URIAuthority.parse("someuser@somehost:"), CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", -1))); assertThat(URIAuthority.parse("someuser@somehost:/blah"), CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", -1))); assertThat(URIAuthority.parse("someuser@somehost:?blah"), CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", -1))); assertThat(URIAuthority.parse("someuser@somehost:#blah"), CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", -1))); assertThat(URIAuthority.parse("someuser@somehost:8080"), CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", 8080))); assertThat(URIAuthority.parse("someuser@somehost:8080/blah"), CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", 8080))); assertThat(URIAuthority.parse("someuser@somehost:8080?blah"), CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", 8080))); assertThat(URIAuthority.parse("someuser@somehost:8080#blah"), CoreMatchers.equalTo(new URIAuthority("someuser", "somehost", 8080))); assertThat(URIAuthority.parse("@somehost:8080"), CoreMatchers.equalTo(new URIAuthority("somehost", 8080))); assertThat(URIAuthority.parse("test:test@localhost:38339"), CoreMatchers.equalTo(new URIAuthority("test:test", "localhost", 38339))); Assertions.assertThrows(URISyntaxException.class, () -> URIAuthority.create("blah@goggle.com:80@google.com/")); } @Test void testCreateFromString() throws Exception { Assertions.assertEquals(new URIAuthority("somehost", 8080), URIAuthority.create("somehost:8080")); Assertions.assertEquals(new URIAuthority("SomeHost", 8080), URIAuthority.create("SomeHost:8080")); Assertions.assertEquals(new URIAuthority("somehost", 1234), URIAuthority.create("somehost:1234")); Assertions.assertEquals(new URIAuthority("somehost", -1), URIAuthority.create("somehost")); Assertions.assertEquals(new URIAuthority("user", "somehost", -1), URIAuthority.create("user@somehost")); Assertions.assertThrows(URISyntaxException.class, () -> URIAuthority.create(" host")); Assertions.assertThrows(URISyntaxException.class, () -> URIAuthority.create("host ")); Assertions.assertThrows(URISyntaxException.class, () -> URIAuthority.create("host :8080")); Assertions.assertThrows(URISyntaxException.class, () -> URIAuthority.create("user @ host:8080")); } @Test void testCreateFromIPv6String() throws Exception { Assertions.assertEquals(new URIAuthority("::1", 8080), URIAuthority.create("[::1]:8080")); Assertions.assertEquals(new URIAuthority("::1", -1), URIAuthority.create("[::1]")); Assertions.assertThrows(URISyntaxException.class, () -> URIAuthority.create("::1")); Assertions.assertThrows(URISyntaxException.class, () -> URIAuthority.create("[::1")); Assertions.assertThrows(URISyntaxException.class, () -> URIAuthority.create("[a]:8080")); } @Test void testIpv6HostToString() { Assertions.assertEquals("[::1]:80", new URIAuthority("::1", 80).toString()); Assertions.assertEquals("user@[::1]:80", new URIAuthority("user", "::1", 80).toString()); Assertions.assertEquals("[::1]", new URIAuthority("::1", -1).toString()); Assertions.assertEquals("user@[::1]", new URIAuthority("user", "::1", -1).toString()); } } httpcore5/src/test/java/org/apache/hc/core5/net/TestURIBuilder.java0100644 0000000 0000000 00000131256 00000250600 024055 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.net; import static org.hamcrest.MatcherAssert.assertThat; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.NameValuePairListMatcher; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.message.BasicNameValuePair; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @SuppressWarnings("deprecation") class TestURIBuilder { private static final String CH_HELLO = "\u0047\u0072\u00FC\u0065\u007A\u0069\u005F\u007A\u00E4\u006D\u00E4"; private static final String RU_HELLO = "\u0412\u0441\u0435\u043C\u005F\u043F\u0440\u0438\u0432\u0435\u0442"; static List parsePath(final CharSequence s) { return URIBuilder.parsePath(s, null); } @Test void testParseSegments() { assertThat(parsePath("/this/that"), CoreMatchers.equalTo(Arrays.asList("this", "that"))); assertThat(parsePath("this/that"), CoreMatchers.equalTo(Arrays.asList("this", "that"))); assertThat(parsePath("this//that"), CoreMatchers.equalTo(Arrays.asList("this", "", "that"))); assertThat(parsePath("this//that/"), CoreMatchers.equalTo(Arrays.asList("this", "", "that", ""))); assertThat(parsePath("this//that/%2fthis%20and%20that"), CoreMatchers.equalTo(Arrays.asList("this", "", "that", "/this and that"))); assertThat(parsePath("this///that//"), CoreMatchers.equalTo(Arrays.asList("this", "", "", "that", "", ""))); assertThat(parsePath("/"), CoreMatchers.equalTo(Collections.singletonList(""))); assertThat(parsePath(""), CoreMatchers.equalTo(Collections.emptyList())); } static String formatPath(final String... pathSegments) { final StringBuilder buf = new StringBuilder(); URIBuilder.formatPath(buf, Arrays.asList(pathSegments), false, null); return buf.toString(); } @Test void testFormatSegments() { assertThat(formatPath("this", "that"), CoreMatchers.equalTo("/this/that")); assertThat(formatPath("this", "", "that"), CoreMatchers.equalTo("/this//that")); assertThat(formatPath("this", "", "that", "/this and that"), CoreMatchers.equalTo("/this//that/%2Fthis%20and%20that")); assertThat(formatPath("this", "", "", "that", "", ""), CoreMatchers.equalTo("/this///that//")); assertThat(formatPath(""), CoreMatchers.equalTo("/")); assertThat(formatPath(), CoreMatchers.equalTo("")); } static List parseQuery(final CharSequence s) { return URIBuilder.parseQuery(s, null, false); } @Test void testParseQuery() { assertThat(parseQuery(""), NameValuePairListMatcher.isEmpty()); assertThat(parseQuery("Name0"), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name0", null))); assertThat(parseQuery("Name1=Value1"), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name1", "Value1"))); assertThat(parseQuery("Name2="), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name2", ""))); assertThat(parseQuery(" Name3 "), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name3", null))); assertThat(parseQuery("Name4=Value%204%21"), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name4", "Value 4!"))); assertThat(parseQuery("Name4=Value%2B4%21"), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name4", "Value+4!"))); assertThat(parseQuery("Name4=Value%204%21%20%214"), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name4", "Value 4! !4"))); assertThat(parseQuery("Name5=aaa&Name6=bbb"), NameValuePairListMatcher.equalsTo( new BasicNameValuePair("Name5", "aaa"), new BasicNameValuePair("Name6", "bbb"))); assertThat(parseQuery("Name7=aaa&Name7=b%2Cb&Name7=ccc"), NameValuePairListMatcher.equalsTo( new BasicNameValuePair("Name7", "aaa"), new BasicNameValuePair("Name7", "b,b"), new BasicNameValuePair("Name7", "ccc"))); assertThat(parseQuery("Name8=xx%2C%20%20yy%20%20%2Czz"), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name8", "xx, yy ,zz"))); assertThat(parseQuery("price=10%20%E2%82%AC"), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("price", "10 \u20AC"))); assertThat(parseQuery("a=b\"c&d=e"), NameValuePairListMatcher.equalsTo( new BasicNameValuePair("a", "b\"c"), new BasicNameValuePair("d", "e"))); assertThat(parseQuery("russian=" + PercentCodec.encode(RU_HELLO, StandardCharsets.UTF_8) + "&swiss=" + PercentCodec.encode(CH_HELLO, StandardCharsets.UTF_8)), NameValuePairListMatcher.equalsTo( new BasicNameValuePair("russian", RU_HELLO), new BasicNameValuePair("swiss", CH_HELLO))); } static String formatQuery(final NameValuePair... params) { final StringBuilder buf = new StringBuilder(); URIBuilder.formatQuery(buf, Arrays.asList(params), null, false); return buf.toString(); } @Test void testFormatQuery() { assertThat(formatQuery(new BasicNameValuePair("Name0", null)), CoreMatchers.equalTo("Name0")); assertThat(formatQuery(new BasicNameValuePair("Name1", "Value1")), CoreMatchers.equalTo("Name1=Value1")); assertThat(formatQuery(new BasicNameValuePair("Name2", "")), CoreMatchers.equalTo("Name2=")); assertThat(formatQuery(new BasicNameValuePair("Name4", "Value 4&")), CoreMatchers.equalTo("Name4=Value%204%26")); assertThat(formatQuery(new BasicNameValuePair("Name4", "Value+4&")), CoreMatchers.equalTo("Name4=Value%2B4%26")); assertThat(formatQuery(new BasicNameValuePair("Name4", "Value 4& =4")), CoreMatchers.equalTo("Name4=Value%204%26%20%3D4")); assertThat(formatQuery( new BasicNameValuePair("Name5", "aaa"), new BasicNameValuePair("Name6", "bbb")), CoreMatchers.equalTo("Name5=aaa&Name6=bbb")); assertThat(formatQuery( new BasicNameValuePair("Name7", "aaa"), new BasicNameValuePair("Name7", "b,b"), new BasicNameValuePair("Name7", "ccc") ), CoreMatchers.equalTo("Name7=aaa&Name7=b%2Cb&Name7=ccc")); assertThat(formatQuery(new BasicNameValuePair("Name8", "xx, yy ,zz")), CoreMatchers.equalTo("Name8=xx%2C%20%20yy%20%20%2Czz")); assertThat(formatQuery( new BasicNameValuePair("russian", RU_HELLO), new BasicNameValuePair("swiss", CH_HELLO)), CoreMatchers.equalTo("russian=" + PercentCodec.encode(RU_HELLO, StandardCharsets.UTF_8) + "&swiss=" + PercentCodec.encode(CH_HELLO, StandardCharsets.UTF_8))); } @Test void testHierarchicalUri() throws Exception { final URI uri = new URI("http", "stuff", "localhost", 80, "/some stuff", "param=stuff", "fragment"); final URIBuilder uribuilder = new URIBuilder(uri).setEncodingPolicy(URIBuilder.EncodingPolicy.ALL_RESERVED); final URI result = uribuilder.build(); Assertions.assertEquals(new URI("http://stuff@localhost:80/some%20stuff?param=stuff#fragment"), result); } @Test void testMutationRemoveFragment() throws Exception { final URI uri = new URI("http://stuff@localhost:80/stuff?param=stuff#fragment"); final URI result = new URIBuilder(uri).setFragment(null).build(); Assertions.assertEquals(new URI("http://stuff@localhost:80/stuff?param=stuff"), result); } @Test void testMutationRemoveUserInfo() throws Exception { final URI uri = new URI("http://stuff@localhost:80/stuff?param=stuff#fragment"); final URI result = new URIBuilder(uri).setUserInfo(null).build(); Assertions.assertEquals(new URI("http://localhost:80/stuff?param=stuff#fragment"), result); } @Test void testMutationRemovePort() throws Exception { final URI uri = new URI("http://stuff@localhost:80/stuff?param=stuff#fragment"); final URI result = new URIBuilder(uri).setPort(-1).build(); Assertions.assertEquals(new URI("http://stuff@localhost/stuff?param=stuff#fragment"), result); } @Test void testOpaqueUri() throws Exception { final URI uri = new URI("stuff", "some-stuff", "fragment"); final URIBuilder uribuilder = new URIBuilder(uri); final URI result = uribuilder.build(); Assertions.assertEquals(uri, result); } @Test void testOpaqueUriMutation() throws Exception { final URI uri = new URI("stuff", "some-stuff", "fragment"); final URIBuilder uribuilder = new URIBuilder(uri).setCustomQuery("param1¶m2=stuff").setFragment(null); Assertions.assertEquals(new URI("stuff:?param1¶m2=stuff"), uribuilder.build()); } @Test void testHierarchicalUriMutation() throws Exception { final URIBuilder uribuilder = new URIBuilder("/").setScheme("http").setHost("localhost").setPort(80).setPath("/stuff"); Assertions.assertEquals(new URI("http://localhost:80/stuff"), uribuilder.build()); } @Test void testLocalhost() throws Exception { // Check that the URI generated by URI builder agrees with that generated by using URI directly final String scheme = "https"; final InetAddress host = InetAddress.getLocalHost(); final String specials = "/abcd!$&*()_-+.,=:;'~@[]?<>|#^%\"{}\\\u00a3`\u00ac\u00a6xyz"; // N.B. excludes space final URI uri = new URI(scheme, specials, host.getHostAddress(), 80, specials, specials, specials); final URI bld = URIBuilder.localhost() .setScheme(scheme) .setUserInfo(specials) .setPath(specials) .setCustomQuery(specials) .setFragment(specials) .build(); Assertions.assertEquals(uri.getHost(), bld.getHost()); Assertions.assertEquals(uri.getUserInfo(), bld.getUserInfo()); Assertions.assertEquals(uri.getPath(), bld.getPath()); Assertions.assertEquals(uri.getQuery(), bld.getQuery()); Assertions.assertEquals(uri.getFragment(), bld.getFragment()); } @Test void testLoopbackAddress() throws Exception { // Check that the URI generated by URI builder agrees with that generated by using URI directly final String scheme = "https"; final InetAddress host = InetAddress.getLoopbackAddress(); final String specials = "/abcd!$&*()_-+.,=:;'~@[]?<>|#^%\"{}\\\u00a3`\u00ac\u00a6xyz"; // N.B. excludes space final URI uri = new URI(scheme, specials, host.getHostAddress(), 80, specials, specials, specials); final URI bld = URIBuilder.loopbackAddress() .setScheme(scheme) .setUserInfo(specials) .setPath(specials) .setCustomQuery(specials) .setFragment(specials) .build(); Assertions.assertEquals(uri.getHost(), bld.getHost()); Assertions.assertEquals(uri.getUserInfo(), bld.getUserInfo()); Assertions.assertEquals(uri.getPath(), bld.getPath()); Assertions.assertEquals(uri.getQuery(), bld.getQuery()); Assertions.assertEquals(uri.getFragment(), bld.getFragment()); } @Test void testEmpty() throws Exception { final URIBuilder uribuilder = new URIBuilder(); final URI result = uribuilder.build(); Assertions.assertEquals(new URI(""), result); } @Test void testEmptyPath() throws Exception { final URIBuilder uribuilder = new URIBuilder("http://thathost"); Assertions.assertTrue(uribuilder.isPathEmpty()); } @Test void testRemoveParameter() throws Exception { final URI uri = new URI("http", null, "localhost", 80, "/", "param=stuff&blah&blah", null); final URIBuilder uribuilder = new URIBuilder(uri); Assertions.assertFalse(uribuilder.isQueryEmpty()); Assertions.assertThrows(NullPointerException.class, () -> uribuilder.removeParameter(null)); uribuilder.removeParameter("DoesNotExist"); Assertions.assertEquals("stuff", uribuilder.getFirstQueryParam("param").getValue()); Assertions.assertNull(uribuilder.getFirstQueryParam("blah").getValue()); uribuilder.removeParameter("blah"); Assertions.assertEquals("stuff", uribuilder.getFirstQueryParam("param").getValue()); Assertions.assertNull(uribuilder.getFirstQueryParam("blah")); uribuilder.removeParameter("param"); Assertions.assertNull(uribuilder.getFirstQueryParam("param")); Assertions.assertTrue(uribuilder.isQueryEmpty()); uribuilder.removeParameter("AlreadyEmpty"); Assertions.assertTrue(uribuilder.isQueryEmpty()); Assertions.assertEquals(new URI("http://localhost:80/"), uribuilder.build()); } @Test void testRemoveQuery() throws Exception { final URI uri = new URI("http", null, "localhost", 80, "/", "param=stuff", null); final URIBuilder uribuilder = new URIBuilder(uri).removeQuery(); final URI result = uribuilder.build(); Assertions.assertEquals(new URI("http://localhost:80/"), result); } @Test void testSetAuthorityFromNamedEndpointHost() throws Exception { final Host host = Host.create("localhost:88"); final URIBuilder uribuilder = new URIBuilder().setScheme(URIScheme.HTTP.id).setAuthority(host); // Check builder Assertions.assertNull(uribuilder.getUserInfo()); Assertions.assertEquals(host.getHostName(), uribuilder.getAuthority().getHostName()); Assertions.assertEquals(host.getHostName(), uribuilder.getHost()); // Check result final URI result = uribuilder.build(); Assertions.assertEquals(host.getHostName(), result.getHost()); Assertions.assertEquals(host.getPort(), result.getPort()); Assertions.assertEquals(new URI("http://localhost:88"), result); } @Test void testSetAuthorityFromNamedEndpointHttpHost() throws Exception { final HttpHost httpHost = HttpHost.create("localhost:88"); final URIBuilder uribuilder = new URIBuilder().setScheme(URIScheme.HTTP.id).setAuthority(httpHost); // Check builder Assertions.assertNull(uribuilder.getUserInfo()); Assertions.assertEquals(httpHost.getHostName(), uribuilder.getAuthority().getHostName()); Assertions.assertEquals(httpHost.getHostName(), uribuilder.getHost()); // Check result final URI result = uribuilder.build(); Assertions.assertEquals(httpHost.getHostName(), result.getHost()); Assertions.assertEquals(httpHost.getPort(), result.getPort()); Assertions.assertEquals(new URI("http://localhost:88"), result); } @Test void testSetAuthorityFromURIAuthority() throws Exception { final URIAuthority authority = URIAuthority.create("u:p@localhost:88"); final URIBuilder uribuilder = new URIBuilder().setScheme(URIScheme.HTTP.id).setAuthority(authority); // Check builder Assertions.assertEquals(authority.getUserInfo(), uribuilder.getAuthority().getUserInfo()); Assertions.assertEquals(authority.getHostName(), uribuilder.getAuthority().getHostName()); Assertions.assertEquals(authority.getHostName(), uribuilder.getHost()); // Check result final URI result = uribuilder.build(); Assertions.assertEquals(authority.getUserInfo(), result.getUserInfo()); Assertions.assertEquals(authority.getHostName(), result.getHost()); Assertions.assertEquals(authority.getPort(), result.getPort()); Assertions.assertEquals(authority.toString(), result.getAuthority()); Assertions.assertEquals(new URI("http://u:p@localhost:88"), result); } @Test void testSetParameter() throws Exception { final URI uri = new URI("http", null, "localhost", 80, "/", "param=stuff&blah&blah", null); final URIBuilder uribuilder = new URIBuilder(uri).setParameter("param", "some other stuff") .setParameter("blah", "blah") .setParameter("blah", "blah2"); final URI result = uribuilder.build(); Assertions.assertEquals(new URI("http://localhost:80/?param=some%20other%20stuff&blah=blah2"), result); } @Test void testGetFirstNamedParameter() throws Exception { final URI uri = new URI("http", null, "localhost", 80, "/", "param=stuff&blah&blah", null); URIBuilder uribuilder = new URIBuilder(uri).setParameter("param", "some other stuff") .setParameter("blah", "blah"); Assertions.assertEquals("some other stuff", uribuilder.getFirstQueryParam("param").getValue()); Assertions.assertEquals("blah", uribuilder.getFirstQueryParam("blah").getValue()); Assertions.assertNull(uribuilder.getFirstQueryParam("DoesNotExist")); // uribuilder = new URIBuilder("http://localhost:80/?param=some%20other%20stuff&blah=blah&blah=blah2"); Assertions.assertEquals("blah", uribuilder.getFirstQueryParam("blah").getValue()); uribuilder.removeQuery(); Assertions.assertNull(uribuilder.getFirstQueryParam("param")); } @Test void testSetParametersWithEmptyArrayArg() throws Exception { final URI uri = new URI("http", null, "localhost", 80, "/test", "param=test", null); final URIBuilder uribuilder = new URIBuilder(uri).setParameters(); final URI result = uribuilder.build(); Assertions.assertEquals(new URI("http://localhost:80/test"), result); } @Test void testSetParametersWithNullArrayArg() throws Exception { final URI uri = new URI("http", null, "localhost", 80, "/test", "param=test", null); final URIBuilder uribuilder = new URIBuilder(uri).setParameters((NameValuePair[]) null); final URI result = uribuilder.build(); Assertions.assertEquals(new URI("http://localhost:80/test"), result); } @Test void testSetParametersWithEmptyList() throws Exception { final URI uri = new URI("http", null, "localhost", 80, "/test", "param=test", null); final URIBuilder uribuilder = new URIBuilder(uri).setParameters(Collections.emptyList()); final URI result = uribuilder.build(); Assertions.assertEquals(new URI("http://localhost:80/test"), result); } @Test void testSetParametersWithNullList() throws Exception { final URI uri = new URI("http", null, "localhost", 80, "/test", "param=test", null); final URIBuilder uribuilder = new URIBuilder(uri).setParameters((List) null); final URI result = uribuilder.build(); Assertions.assertEquals(new URI("http://localhost:80/test"), result); } @Test void testParameterWithSpecialChar() throws Exception { final URI uri = new URI("http", null, "localhost", 80, "/", "param=stuff", null); final URIBuilder uribuilder = new URIBuilder(uri).addParameter("param", "1 + 1 = 2") .addParameter("param", "blah&blah"); final URI result = uribuilder.build(); Assertions.assertEquals(new URI("http://localhost:80/?param=stuff¶m=1%20%2B%201%20%3D%202&" + "param=blah%26blah"), result); } @Test void testAddParameter() throws Exception { final URI uri = new URI("http", null, "localhost", 80, "/", "param=stuff&blah&blah", null); final URIBuilder uribuilder = new URIBuilder(uri).addParameter("param", "some other stuff") .addParameter("blah", "blah"); final URI result = uribuilder.build(); Assertions.assertEquals(new URI("http://localhost:80/?param=stuff&blah&blah&" + "param=some%20other%20stuff&blah=blah"), result); } @Test void testQueryEncoding() throws Exception { final URI uri1 = new URI("https://somehost.com/stuff?client_id=1234567890" + "&redirect_uri=https%3A%2F%2Fsomehost.com%2Fblah%20blah%2F"); final URI uri2 = new URIBuilder("https://somehost.com/stuff") .addParameter("client_id","1234567890") .addParameter("redirect_uri","https://somehost.com/blah blah/").build(); Assertions.assertEquals(uri1, uri2); } @Test void testQueryAndParameterEncoding() throws Exception { final URI uri1 = new URI("https://somehost.com/stuff?param1=12345¶m2=67890"); final URI uri2 = new URIBuilder("https://somehost.com/stuff") .setCustomQuery("this&that") .addParameter("param1","12345") .addParameter("param2","67890").build(); Assertions.assertEquals(uri1, uri2); } @Test void testPathEncoding() throws Exception { final URI uri1 = new URI("https://somehost.com/some%20path%20with%20blanks/"); final URI uri2 = new URIBuilder() .setScheme("https") .setHost("somehost.com") .setPath("/some path with blanks/") .build(); Assertions.assertEquals(uri1, uri2); } @Test void testFragmentEncoding() throws Exception { final URI uri1 = new URI("https://somehost.com#some%20fragment%20with%20all%20sorts%20of%20$tuff%20in%20it!!!"); final URI uri2 = new URIBuilder() .setScheme("https") .setHost("somehost.com") .setFragment("some fragment with all sorts of $tuff in it!!!") .build(); Assertions.assertEquals(uri1, uri2); } @Test void testAgainstURI() throws Exception { // Check that the URI generated by URI builder agrees with that generated by using URI directly final String scheme = "https"; final String host = "localhost"; final String specials = "/abcd!$&*()_-+.,=:;'~@[]?<>|#^%\"{}\\\u00a3`\u00ac\u00a6xyz"; // N.B. excludes space final URI uri = new URI(scheme, specials, host, 80, specials, specials, specials); final URI bld = new URIBuilder() .setScheme(scheme) .setHost(host) .setUserInfo(specials) .setPath(specials) .setCustomQuery(specials) .setFragment(specials) .build(); Assertions.assertEquals(uri.getHost(), bld.getHost()); Assertions.assertEquals(uri.getUserInfo(), bld.getUserInfo()); Assertions.assertEquals(uri.getPath(), bld.getPath()); Assertions.assertEquals(uri.getQuery(), bld.getQuery()); Assertions.assertEquals(uri.getFragment(), bld.getFragment()); } @Test void testBuildAddParametersUTF8() throws Exception { assertAddParameters(StandardCharsets.UTF_8); } @Test void testBuildAddParametersISO88591() throws Exception { assertAddParameters(StandardCharsets.ISO_8859_1); } void assertAddParameters(final Charset charset) throws Exception { final URI uri = new URIBuilder("https://somehost.com/stuff") .setCharset(charset) .addParameters(createParameterList()).build(); assertBuild(charset, uri); // null addParameters final URI uri2 = new URIBuilder("https://somehost.com/stuff") .setCharset(charset) .addParameters(null).build(); Assertions.assertEquals("https://somehost.com/stuff", uri2.toString()); } @Test void testBuildSetParametersUTF8() throws Exception { assertSetParameters(StandardCharsets.UTF_8); } @Test void testBuildSetParametersISO88591() throws Exception { assertSetParameters(StandardCharsets.ISO_8859_1); } void assertSetParameters(final Charset charset) throws Exception { final URI uri = new URIBuilder("https://somehost.com/stuff") .setCharset(charset) .setParameters(createParameterList()).build(); assertBuild(charset, uri); } void assertBuild(final Charset charset, final URI uri) { final String encodedData1 = PercentCodec.encode("\"1\u00aa position\"", charset); final String encodedData2 = PercentCodec.encode("Jos\u00e9 Abra\u00e3o", charset); final String uriExpected = String.format("https://somehost.com/stuff?parameter1=value1¶meter2=%s¶meter3=%s", encodedData1, encodedData2); Assertions.assertEquals(uriExpected, uri.toString()); } private List createParameterList() { final List parameters = new ArrayList<>(); parameters.add(new BasicNameValuePair("parameter1", "value1")); parameters.add(new BasicNameValuePair("parameter2", "\"1\u00aa position\"")); parameters.add(new BasicNameValuePair("parameter3", "Jos\u00e9 Abra\u00e3o")); return parameters; } @Test void testMalformedPath() throws Exception { final String path = "@notexample.com/mypath"; final URI uri = new URIBuilder(path).setHost("example.com").build(); Assertions.assertEquals("example.com", uri.getHost()); } @Test void testRelativePath() throws Exception { final URI uri = new URIBuilder("./mypath").build(); Assertions.assertEquals(new URI("./mypath"), uri); } @Test void testRelativePathWithAuthority() throws Exception { final URI uri = new URIBuilder("./mypath").setHost("somehost").setScheme("http").build(); Assertions.assertEquals(new URI("http://somehost/./mypath"), uri); } @Test void testTolerateNullInput() throws Exception { assertThat(new URIBuilder() .setScheme(null) .setHost("localhost") .setUserInfo(null) .setPort(8443) .setPath(null) .setCustomQuery(null) .setFragment(null) .build(), CoreMatchers.equalTo(URI.create("//localhost:8443"))); } @Test void testTolerateBlankInput() throws Exception { assertThat(new URIBuilder() .setScheme("") .setHost("localhost") .setUserInfo("") .setPort(8443) .setPath("") .setPath("") .setCustomQuery("") .setFragment("") .build(), CoreMatchers.equalTo(URI.create("//localhost:8443"))); } @Test void testHttpHost() throws Exception { final HttpHost httpHost = new HttpHost("http", "example.com", 1234); final URIBuilder uribuilder = new URIBuilder(); uribuilder.setHttpHost(httpHost); Assertions.assertEquals(URI.create("http://example.com:1234"), uribuilder.build()); } @Test void testSetHostWithReservedChars() throws Exception { final URIBuilder uribuilder = new URIBuilder(); uribuilder.setScheme("http").setHost("!example!.com"); Assertions.assertEquals(URI.create("http://%21example%21.com"), uribuilder.build()); } @Test void testGetHostWithReservedChars() throws Exception { final URIBuilder uribuilder = new URIBuilder("http://someuser%21@%21example%21.com/"); Assertions.assertEquals("!example!.com", uribuilder.getHost()); Assertions.assertEquals("someuser!", uribuilder.getUserInfo()); } @Test void testMultipleLeadingPathSlashes() throws Exception { final URI uri = new URIBuilder() .setScheme("ftp") .setHost("somehost") .setPath("//blah//blah") .build(); assertThat(uri, CoreMatchers.equalTo(URI.create("ftp://somehost//blah//blah"))); } @Test void testNoAuthorityAndPath() throws Exception { final URI uri = new URIBuilder() .setScheme("file") .setPath("/blah") .build(); assertThat(uri, CoreMatchers.equalTo(URI.create("file:/blah"))); } @Test void testSetPathSegmentList() throws Exception { final URI uri = new URIBuilder() .setScheme("https") .setHost("somehost") .setPathSegments(Arrays.asList("api", "products")) .build(); assertThat(uri, CoreMatchers.equalTo(URI.create("https://somehost/api/products"))); } @Test void testSetPathSegmentsVarargs() throws Exception { final URI uri = new URIBuilder() .setScheme("https") .setHost("somehost") .setPathSegments("api", "products") .build(); assertThat(uri, CoreMatchers.equalTo(URI.create("https://somehost/api/products"))); } @Test void testSetPathSegmentsRootlessList() throws Exception { final URI uri = new URIBuilder() .setScheme("file") .setPathSegmentsRootless(Arrays.asList("dir", "foo")) .build(); assertThat(uri, CoreMatchers.equalTo(URI.create("file:dir/foo"))); } @Test void testSetPathSegmentsRootlessVarargs() throws Exception { final URI uri = new URIBuilder() .setScheme("file") .setPathSegmentsRootless("dir", "foo") .build(); assertThat(uri, CoreMatchers.equalTo(URI.create("file:dir/foo"))); } @Test void testAppendToExistingPath() throws Exception { final URI uri = new URIBuilder() .setScheme("https") .setHost("somehost") .setPath("api") .appendPath("v1/resources") .appendPath("idA") .build(); assertThat(uri, CoreMatchers.equalTo(URI.create("https://somehost/api/v1/resources/idA"))); } @Test void testAppendToNonExistingPath() throws Exception { final URI uri = new URIBuilder() .setScheme("https") .setHost("somehost") .appendPath("api/v2/customers") .appendPath("idA") .build(); assertThat(uri, CoreMatchers.equalTo(URI.create("https://somehost/api/v2/customers/idA"))); } @Test void testAppendNullToExistingPath() throws Exception { final URI uri = new URIBuilder() .setScheme("https") .setHost("somehost") .setPath("api") .appendPath(null) .build(); assertThat(uri, CoreMatchers.equalTo(URI.create("https://somehost/api"))); } @Test void testAppendNullToNonExistingPath() throws Exception { final URI uri = new URIBuilder() .setScheme("https") .setHost("somehost") .appendPath(null) .build(); assertThat(uri, CoreMatchers.equalTo(URI.create("https://somehost"))); } @Test void testAppendSegmentsVarargsToExistingPath() throws Exception { final URI uri = new URIBuilder() .setScheme("https") .setHost("myhost") .setPath("api") .appendPathSegments("v3", "products") .appendPathSegments("idA") .build(); assertThat(uri, CoreMatchers.equalTo(URI.create("https://myhost/api/v3/products/idA"))); } @Test void testAppendSegmentsVarargsToNonExistingPath() throws Exception { final URI uri = new URIBuilder() .setScheme("https") .setHost("somehost") .appendPathSegments("api", "v2", "customers") .appendPathSegments("idA") .build(); assertThat(uri, CoreMatchers.equalTo(URI.create("https://somehost/api/v2/customers/idA"))); } @Test void testAppendNullSegmentsVarargs() throws Exception { final String pathSegment = null; final URI uri = new URIBuilder() .setScheme("https") .setHost("somehost") .appendPathSegments(pathSegment) .build(); assertThat(uri, CoreMatchers.equalTo(URI.create("https://somehost/"))); } @Test void testAppendSegmentsListToExistingPath() throws Exception { final URI uri = new URIBuilder() .setScheme("http") .setHost("myhost") .setPath("api") .appendPathSegments(Arrays.asList("v3", "products")) .build(); assertThat(uri, CoreMatchers.equalTo(URI.create("http://myhost/api/v3/products"))); } @Test void testAppendSegmentsListToNonExistingPath() throws Exception { final URI uri = new URIBuilder() .setScheme("http") .setHost("myhost") .appendPathSegments(Arrays.asList("api", "v3", "customers")) .build(); assertThat(uri, CoreMatchers.equalTo(URI.create("http://myhost/api/v3/customers"))); } @Test void testAppendNullSegmentsList() throws Exception { final List pathSegments = null; final URI uri = new URIBuilder() .setScheme("http") .setHost("myhost") .appendPathSegments(pathSegments) .build(); assertThat(uri, CoreMatchers.equalTo(URI.create("http://myhost"))); } @Test void testNoAuthorityAndPathSegments() throws Exception { final URI uri = new URIBuilder() .setScheme("file") .setPathSegments("this", "that") .build(); assertThat(uri, CoreMatchers.equalTo(URI.create("file:/this/that"))); } @Test void testNoAuthorityAndRootlessPath() throws Exception { final URI uri = new URIBuilder() .setScheme("file") .setPath("blah") .build(); assertThat(uri, CoreMatchers.equalTo(URI.create("file:blah"))); } @Test void testNoAuthorityAndRootlessPathSegments() throws Exception { final URI uri = new URIBuilder() .setScheme("file") .setPathSegmentsRootless("this", "that") .build(); assertThat(uri, CoreMatchers.equalTo(URI.create("file:this/that"))); } @Test void testOpaque() throws Exception { final URIBuilder uriBuilder = new URIBuilder("http://host.com"); final URI uri = uriBuilder.build(); assertThat(uriBuilder.isOpaque(), CoreMatchers.equalTo(uri.isOpaque())); } @Test void testAddParameterEncodingEquivalence() throws Exception { final URI uri = new URI("http", null, "localhost", 80, "/", "param=stuff with spaces", null); final URIBuilder uribuilder = new URIBuilder().setScheme("http").setHost("localhost").setPort(80).setPath("/").addParameter( "param", "stuff with spaces"); final URI result = uribuilder.build(); Assertions.assertEquals(uri, result); } @Test void testSchemeSpecificPartParametersNull() throws Exception { final URIBuilder uribuilder = new URIBuilder("http://host.com").setParameter("par", "parvalue") .setSchemeSpecificPart("", (NameValuePair)null); Assertions.assertEquals(new URI("http://host.com?par=parvalue"), uribuilder.build()); } @Test void testSchemeSpecificPartSetGet() { final URIBuilder uribuilder = new URIBuilder().setSchemeSpecificPart("specificpart"); Assertions.assertEquals("specificpart", uribuilder.getSchemeSpecificPart()); } /** Common use case: mailto: scheme. See https://tools.ietf.org/html/rfc6068#section-2 */ @Test void testSchemeSpecificPartNameValuePairByRFC6068Sample() throws Exception { final URIBuilder uribuilder = new URIBuilder().setScheme("mailto") .setSchemeSpecificPart("my@email.server", new BasicNameValuePair("subject", "mail subject")); final String result = uribuilder.build().toString(); Assertions.assertTrue(result.contains("my@email.server"), "mail address as scheme specific part expected"); Assertions.assertTrue(result.contains("mail%20subject"), "correct parameter encoding expected for that scheme"); } /** Common use case: mailto: scheme. See https://tools.ietf.org/html/rfc6068#section-2 */ @Test void testSchemeSpecificPartNameValuePairListByRFC6068Sample() throws Exception { final List parameters = new ArrayList<>(); parameters.add(new BasicNameValuePair("subject", "mail subject")); final URIBuilder uribuilder = new URIBuilder().setScheme("mailto").setSchemeSpecificPart("my@email.server", parameters); final String result = uribuilder.build().toString(); Assertions.assertTrue(result.contains("my@email.server"), "mail address as scheme specific part expected"); Assertions.assertTrue(result.contains("mail%20subject"), "correct parameter encoding expected for that scheme"); } @Test void testOptimize() throws Exception { Assertions.assertEquals("example://a/b/c/%7Bfoo%7D", new URIBuilder("eXAMPLE://a/./b/../b/%63/%7bfoo%7d").optimize().build().toASCIIString()); Assertions.assertEquals("http://www.example.com/%3C", new URIBuilder("http://www.example.com/%3c").optimize().build().toASCIIString()); Assertions.assertEquals("http://www.example.com/", new URIBuilder("HTTP://www.EXAMPLE.com/").optimize().build().toASCIIString()); Assertions.assertEquals("http://www.example.com/a%2F", new URIBuilder("http://www.example.com/a%2f").optimize().build().toASCIIString()); Assertions.assertEquals("http://www.example.com/?a%2F", new URIBuilder("http://www.example.com/?a%2f").optimize().build().toASCIIString()); Assertions.assertEquals("http://www.example.com/?q=%26", new URIBuilder("http://www.example.com/?q=%26").optimize().build().toASCIIString()); Assertions.assertEquals("http://www.example.com/%23?q=%26", new URIBuilder("http://www.example.com/%23?q=%26").optimize().build().toASCIIString()); Assertions.assertEquals("http://www.example.com/blah-%28%20-blah-%20%26%20-blah-%20%29-blah/", new URIBuilder("http://www.example.com/blah-%28%20-blah-%20&%20-blah-%20)-blah/").optimize().build().toASCIIString()); Assertions.assertEquals("../../.././", new URIBuilder("../../.././").optimize().build().toASCIIString()); Assertions.assertEquals("file:../../.././", new URIBuilder("file:../../.././").optimize().build().toASCIIString()); Assertions.assertEquals("http://host/", new URIBuilder("http://host/../../.././").optimize().build().toASCIIString()); Assertions.assertThrows(URISyntaxException.class, () -> new URIBuilder("http:///../../.././").optimize().build().toASCIIString()); } @Test void testIpv6Host() throws Exception { final URIBuilder builder = new URIBuilder("https://[::1]:432/path"); final URI uri = builder.build(); Assertions.assertEquals(432, builder.getPort()); Assertions.assertEquals(432, uri.getPort()); Assertions.assertEquals("https", builder.getScheme()); Assertions.assertEquals("https", uri.getScheme()); Assertions.assertEquals("::1", builder.getHost()); Assertions.assertEquals("[::1]", uri.getHost()); Assertions.assertEquals("/path", builder.getPath()); Assertions.assertEquals("/path", uri.getPath()); } @Test void testIpv6HostWithPortUpdate() throws Exception { // Updating the port clears URIBuilder.encodedSchemeSpecificPart // and bypasses the fast/simple path which preserves input. final URIBuilder builder = new URIBuilder("https://[::1]:432/path").setPort(123); final URI uri = builder.build(); Assertions.assertEquals(123, builder.getPort()); Assertions.assertEquals(123, uri.getPort()); Assertions.assertEquals("https", builder.getScheme()); Assertions.assertEquals("https", uri.getScheme()); Assertions.assertEquals("::1", builder.getHost()); Assertions.assertEquals("[::1]", uri.getHost()); Assertions.assertEquals("/path", builder.getPath()); Assertions.assertEquals("/path", uri.getPath()); } @Test void testBuilderWithUnbracketedIpv6Host() throws Exception { final URIBuilder builder = new URIBuilder().setScheme("https").setHost("::1").setPort(443).setPath("/path"); final URI uri = builder.build(); Assertions.assertEquals("https", builder.getScheme()); Assertions.assertEquals("https", uri.getScheme()); Assertions.assertEquals(443, builder.getPort()); Assertions.assertEquals(443, uri.getPort()); Assertions.assertEquals("::1", builder.getHost()); Assertions.assertEquals("[::1]", uri.getHost()); Assertions.assertEquals("/path", builder.getPath()); Assertions.assertEquals("/path", uri.getPath()); } @Test void testHttpsUriWithEmptyHost() { final URIBuilder uribuilder = new URIBuilder() .setScheme("https") .setUserInfo("stuff") .setHost("") .setPort(80) .setPath("/some stuff") .setParameter("param", "stuff") .setFragment("fragment"); Assertions.assertThrows(URISyntaxException.class, uribuilder::build); } @Test void testHttpUriWithEmptyHost() { final URIBuilder uribuilder = new URIBuilder() .setScheme("http") .setUserInfo("stuff") .setHost("") .setPort(80) .setPath("/some stuff") .setParameter("param", "stuff") .setFragment("fragment"); Assertions.assertThrows(URISyntaxException.class, uribuilder::build); } @Test void testSetPlusAsBlank() throws Exception { // Case 1: Plus as blank, "+" should be treated as space URIBuilder uriBuilder = new URIBuilder("http://localhost?param=hello+world") .setPlusAsBlank(true); List params = uriBuilder.getQueryParams(); Assertions.assertEquals("hello world", params.get(0).getValue()); // Case 2: Plus as plus, "+" should remain "+" uriBuilder = new URIBuilder("http://localhost?param=hello+world") .setPlusAsBlank(false); params = uriBuilder.getQueryParams(); Assertions.assertEquals("hello+world", params.get(0).getValue()); // Case 3: '%20' always interpreted as space uriBuilder = new URIBuilder("http://localhost?param=hello%20world") .setPlusAsBlank(true); params = uriBuilder.getQueryParams(); Assertions.assertEquals("hello world", params.get(0).getValue()); uriBuilder = new URIBuilder("http://localhost?param=hello%20world") .setPlusAsBlank(false); params = uriBuilder.getQueryParams(); Assertions.assertEquals("hello world", params.get(0).getValue()); } @Test void testCustomQueryEncoding() throws Exception { final String query = "query param:!@/?\""; final String expectedEncodedQuery = "query%20param:!@/?%22"; final URI uri = new URIBuilder() .setScheme("http") .setHost("example.com") .setCustomQuery(query) .setEncodingPolicy(URIBuilder.EncodingPolicy.RFC_3986) .build(); Assertions.assertEquals(expectedEncodedQuery, uri.getRawQuery()); } } httpcore5/src/test/java/org/apache/hc/core5/net/TestWWWFormCodec.java0100644 0000000 0000000 00000014276 00000250600 024357 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.net; import static org.hamcrest.MatcherAssert.assertThat; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.NameValuePairListMatcher; import org.apache.hc.core5.http.message.BasicNameValuePair; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; class TestWWWFormCodec { private static final String CH_HELLO = "\u0047\u0072\u00FC\u0065\u007A\u0069\u005F\u007A\u00E4\u006D\u00E4"; private static final String RU_HELLO = "\u0412\u0441\u0435\u043C\u005F\u043F\u0440\u0438\u0432\u0435\u0442"; private static List parse(final String params) { return WWWFormCodec.parse(params, StandardCharsets.UTF_8); } @Test void testParse() { assertThat(parse(""), NameValuePairListMatcher.isEmpty()); assertThat(parse("Name0"), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name0", null))); assertThat(parse("Name1=Value1"), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name1", "Value1"))); assertThat(parse("Name2="), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name2", ""))); assertThat(parse(" Name3 "), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name3", null))); assertThat(parse("Name4=Value%204%21"), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name4", "Value 4!"))); assertThat(parse("Name4=Value%2B4%21"), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name4", "Value+4!"))); assertThat(parse("Name4=Value%204%21%20%214"), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name4", "Value 4! !4"))); assertThat(parse("Name5=aaa&Name6=bbb"), NameValuePairListMatcher.equalsTo( new BasicNameValuePair("Name5", "aaa"), new BasicNameValuePair("Name6", "bbb"))); assertThat(parse("Name7=aaa&Name7=b%2Cb&Name7=ccc"), NameValuePairListMatcher.equalsTo( new BasicNameValuePair("Name7", "aaa"), new BasicNameValuePair("Name7", "b,b"), new BasicNameValuePair("Name7", "ccc"))); assertThat(parse("Name8=xx%2C%20%20yy%20%20%2Czz"), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name8", "xx, yy ,zz"))); assertThat(parse("price=10%20%E2%82%AC"), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("price", "10 \u20AC"))); assertThat(parse("a=b\"c&d=e"), NameValuePairListMatcher.equalsTo( new BasicNameValuePair("a", "b\"c"), new BasicNameValuePair("d", "e"))); assertThat(parse("russian=" + PercentCodec.encode(RU_HELLO, StandardCharsets.UTF_8) + "&swiss=" + PercentCodec.encode(CH_HELLO, StandardCharsets.UTF_8)), NameValuePairListMatcher.equalsTo( new BasicNameValuePair("russian", RU_HELLO), new BasicNameValuePair("swiss", CH_HELLO))); } private static String format(final NameValuePair... nvps) { return WWWFormCodec.format(Arrays.asList(nvps), StandardCharsets.UTF_8); } @Test void testFormat() { assertThat(format(new BasicNameValuePair("Name0", null)), CoreMatchers.equalTo("Name0")); assertThat(format(new BasicNameValuePair("Name1", "Value1")), CoreMatchers.equalTo("Name1=Value1")); assertThat(format(new BasicNameValuePair("Name2", "")), CoreMatchers.equalTo("Name2=")); assertThat(format(new BasicNameValuePair("Name4", "Value 4&")), CoreMatchers.equalTo("Name4=Value+4%26")); assertThat(format(new BasicNameValuePair("Name4", "Value+4&")), CoreMatchers.equalTo("Name4=Value%2B4%26")); assertThat(format(new BasicNameValuePair("Name4", "Value 4& =4")), CoreMatchers.equalTo("Name4=Value+4%26+%3D4")); assertThat(format( new BasicNameValuePair("Name5", "aaa"), new BasicNameValuePair("Name6", "bbb")), CoreMatchers.equalTo("Name5=aaa&Name6=bbb")); assertThat(format( new BasicNameValuePair("Name7", "aaa"), new BasicNameValuePair("Name7", "b,b"), new BasicNameValuePair("Name7", "ccc") ), CoreMatchers.equalTo("Name7=aaa&Name7=b%2Cb&Name7=ccc")); assertThat(format(new BasicNameValuePair("Name8", "xx, yy ,zz")), CoreMatchers.equalTo("Name8=xx%2C++yy++%2Czz")); assertThat(format( new BasicNameValuePair("russian", RU_HELLO), new BasicNameValuePair("swiss", CH_HELLO)), CoreMatchers.equalTo("russian=" + PercentCodec.encode(RU_HELLO, StandardCharsets.UTF_8) + "&swiss=" + PercentCodec.encode(CH_HELLO, StandardCharsets.UTF_8))); } } httpcore5/src/test/java/org/apache/hc/core5/pool/0040755 0000000 0000000 00000000000 00000250600 020562 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/pool/FakeConnection.java0100644 0000000 0000000 00000004626 00000250600 024320 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.ModalCloseable; final class FakeConnection implements ModalCloseable { private final long closeSleepMs; private final AtomicInteger closes = new AtomicInteger(0); private final CountDownLatch closedLatch = new CountDownLatch(1); FakeConnection() { this(0); } FakeConnection(final long closeSleepMs) { this.closeSleepMs = closeSleepMs; } @Override public void close(final CloseMode closeMode) { if (closeSleepMs > 0) { try { Thread.sleep(closeSleepMs); } catch (final InterruptedException ignore) { Thread.currentThread().interrupt(); } } closes.incrementAndGet(); closedLatch.countDown(); } @Override public void close() { close(CloseMode.GRACEFUL); } int closeCount() { return closes.get(); } boolean awaitClosed(final long ms) throws InterruptedException { return closedLatch.await(ms, TimeUnit.MILLISECONDS); } } httpcore5/src/test/java/org/apache/hc/core5/pool/RouteSegmentedConnPoolTest.java0100644 0000000 0000000 00000041153 00000250600 026670 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; 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.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; 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.AtomicLong; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.ModalCloseable; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Test; public class RouteSegmentedConnPoolTest { private static RouteSegmentedConnPool newPool( final int defPerRoute, final int maxTotal, final TimeValue ttl, final PoolReusePolicy reuse, final DisposalCallback disposal) { return new RouteSegmentedConnPool<>(defPerRoute, maxTotal, ttl, reuse, disposal); } @Test void basicLeaseReleaseAndHandoff() throws Exception { final DisposalCallback disposal = FakeConnection::close; final RouteSegmentedConnPool pool = newPool(2, 2, TimeValue.NEG_ONE_MILLISECOND, PoolReusePolicy.LIFO, disposal); final PoolEntry e1 = pool.lease("r1", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); assertNotNull(e1); assertEquals("r1", e1.getRoute()); assertFalse(e1.hasConnection()); e1.assignConnection(new FakeConnection()); e1.updateState("A"); e1.updateExpiry(TimeValue.ofSeconds(30)); pool.release(e1, true); final Future> f2 = pool.lease("r1", "A", Timeout.ofSeconds(1), null); final PoolEntry e2 = f2.get(1, TimeUnit.SECONDS); assertSame(e1, e2, "Should receive same entry via direct hand-off"); pool.release(e2, true); pool.close(CloseMode.IMMEDIATE); } @Test void perRouteAndTotalLimits() throws Exception { final DisposalCallback disposal = FakeConnection::close; final RouteSegmentedConnPool pool = newPool(1, 2, TimeValue.NEG_ONE_MILLISECOND, PoolReusePolicy.LIFO, disposal); final PoolEntry r1a = pool.lease("r1", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); final PoolEntry r2a = pool.lease("r2", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); final Future> blocked = pool.lease("r1", null, Timeout.ofMilliseconds(150), null); final ExecutionException ex = assertThrows( ExecutionException.class, () -> blocked.get(400, TimeUnit.MILLISECONDS)); assertInstanceOf(TimeoutException.class, ex.getCause()); assertEquals("Lease timed out", ex.getCause().getMessage()); r1a.assignConnection(new FakeConnection()); r1a.updateExpiry(TimeValue.ofSeconds(5)); pool.release(r1a, true); final PoolEntry r1b = pool.lease("r1", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); assertNotNull(r1b); pool.release(r2a, false); // drop pool.release(r1b, false); pool.close(CloseMode.IMMEDIATE); } @Test void stateCompatibilityNullMatchesAnything() throws Exception { final RouteSegmentedConnPool pool = newPool(1, 1, TimeValue.NEG_ONE_MILLISECOND, PoolReusePolicy.LIFO, FakeConnection::close); final PoolEntry e = pool.lease("r", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); e.assignConnection(new FakeConnection()); e.updateState("X"); e.updateExpiry(TimeValue.ofSeconds(30)); pool.release(e, true); // waiter with null state must match final PoolEntry got = pool.lease("r", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); assertSame(e, got); pool.release(got, false); pool.close(CloseMode.IMMEDIATE); } @Test void closeIdleRemovesStaleAvailable() throws Exception { final RouteSegmentedConnPool pool = newPool(2, 2, TimeValue.NEG_ONE_MILLISECOND, PoolReusePolicy.LIFO, FakeConnection::close); final PoolEntry e = pool.lease("r", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); e.assignConnection(new FakeConnection()); e.updateExpiry(TimeValue.ofSeconds(30)); pool.release(e, true); // sleep to make it idle Thread.sleep(120); pool.closeIdle(TimeValue.ofMilliseconds(50)); final PoolStats stats = pool.getStats("r"); assertEquals(0, stats.getAvailable()); pool.close(CloseMode.IMMEDIATE); } @Test void closeExpiredHonorsEntryExpiryOrTtl() throws Exception { // TTL = 100ms, so entries become past-ttl quickly final RouteSegmentedConnPool pool = newPool(1, 1, TimeValue.ofMilliseconds(100), PoolReusePolicy.LIFO, FakeConnection::close); final PoolEntry e = pool.lease("r", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); e.assignConnection(new FakeConnection()); // keep alive "far", TTL will still kill it e.updateExpiry(TimeValue.ofSeconds(10)); pool.release(e, true); Thread.sleep(150); pool.closeExpired(); final PoolStats stats = pool.getStats("r"); assertEquals(0, stats.getAvailable(), "Expired/TTL entry should be gone"); pool.close(CloseMode.IMMEDIATE); } @Test void waiterTimesOutAndIsFailed() throws Exception { final RouteSegmentedConnPool pool = newPool(1, 1, TimeValue.NEG_ONE_MILLISECOND, PoolReusePolicy.LIFO, FakeConnection::close); // Occupy single slot and don't release final PoolEntry e = pool.lease("r", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); final Future> waiter = pool.lease("r", null, Timeout.ofMilliseconds(150), null); final ExecutionException ex = assertThrows( ExecutionException.class, () -> waiter.get(500, TimeUnit.MILLISECONDS)); assertInstanceOf(TimeoutException.class, ex.getCause()); assertEquals("Lease timed out", ex.getCause().getMessage()); // cleanup pool.release(e, false); pool.close(CloseMode.IMMEDIATE); } @Test void poolCloseCancelsWaitersAndDrainsAvailable() throws Exception { final RouteSegmentedConnPool pool = newPool(1, 1, TimeValue.NEG_ONE_MILLISECOND, PoolReusePolicy.LIFO, FakeConnection::close); // Consume the only slot so the next lease becomes a waiter final Future> first = pool.lease("r", null, Timeout.ofSeconds(5), null); first.get(); // allocated immediately, not released // Now this one queues as a waiter final Future> waiter = pool.lease("r", null, Timeout.ofSeconds(5), null); pool.close(CloseMode.IMMEDIATE); final ExecutionException ex = assertThrows(ExecutionException.class, waiter::get); assertInstanceOf(TimeoutException.class, ex.getCause()); assertEquals("Pool closed", ex.getCause().getMessage()); } @Test void reusePolicyLifoVsFifoIsObservable() throws Exception { final RouteSegmentedConnPool lifo = newPool(2, 2, TimeValue.NEG_ONE_MILLISECOND, PoolReusePolicy.LIFO, FakeConnection::close); final PoolEntry a = lifo.lease("r", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); final PoolEntry b = lifo.lease("r", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); a.assignConnection(new FakeConnection()); a.updateExpiry(TimeValue.ofSeconds(10)); lifo.release(a, true); b.assignConnection(new FakeConnection()); b.updateExpiry(TimeValue.ofSeconds(10)); lifo.release(b, true); final PoolEntry firstLifo = lifo.lease("r", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); assertSame(b, firstLifo, "LIFO should return last released"); lifo.release(firstLifo, false); lifo.close(CloseMode.IMMEDIATE); final RouteSegmentedConnPool fifo = newPool(2, 2, TimeValue.NEG_ONE_MILLISECOND, PoolReusePolicy.FIFO, FakeConnection::close); final PoolEntry a2 = fifo.lease("r", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); final PoolEntry b2 = fifo.lease("r", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); a2.assignConnection(new FakeConnection()); a2.updateExpiry(TimeValue.ofSeconds(10)); fifo.release(a2, true); b2.assignConnection(new FakeConnection()); b2.updateExpiry(TimeValue.ofSeconds(10)); fifo.release(b2, true); final PoolEntry firstFifo = fifo.lease("r", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); assertSame(a2, firstFifo, "FIFO should return first released"); fifo.release(firstFifo, false); fifo.close(CloseMode.IMMEDIATE); } @Test void disposalIsCalledOnDiscard() throws Exception { final List closed = new ArrayList<>(); final CountDownLatch disposed = new CountDownLatch(1); final DisposalCallback disposal = (c, m) -> { try { c.close(m); } finally { closed.add(c); disposed.countDown(); } }; final RouteSegmentedConnPool pool = newPool(1, 1, TimeValue.NEG_ONE_MILLISECOND, PoolReusePolicy.LIFO, disposal); final PoolEntry e = pool.lease("r", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); final FakeConnection conn = new FakeConnection(); e.assignConnection(conn); pool.release(e, false); // Wait for async disposer to run assertTrue(disposed.await(2, TimeUnit.SECONDS), "Disposal did not complete in time"); assertEquals(1, closed.size()); assertEquals(1, closed.get(0).closeCount()); pool.close(CloseMode.IMMEDIATE); } @Test void slowDisposalDoesNotBlockOtherRoutes() throws Exception { final CountDownLatch disposed = new CountDownLatch(1); final AtomicLong closedAt = new AtomicLong(0L); final DisposalCallback disposal = (c, m) -> { try { c.close(m); // FakeConnection sleeps closeDelayMs internally } finally { closedAt.set(System.nanoTime()); disposed.countDown(); } }; final RouteSegmentedConnPool pool = newPool(2, 2, TimeValue.NEG_ONE_MILLISECOND, PoolReusePolicy.LIFO, disposal); final PoolEntry e1 = pool.lease("r1", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); e1.assignConnection(new FakeConnection(600)); // close sleeps ~600ms final long startDiscard = System.nanoTime(); pool.release(e1, false); // triggers async disposal // Lease on another route must not be blocked by slow disposal final long t0 = System.nanoTime(); final PoolEntry e2 = pool.lease("r2", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); final long tLeaseMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t0); assertTrue(tLeaseMs < 200, "Other route lease blocked by disposal: " + tLeaseMs + "ms"); pool.release(e2, false); // Wait for disposer to finish, then assert the slow path really took ~600ms assertTrue(disposed.await(2, TimeUnit.SECONDS), "Disposal did not complete in time"); final long discardMs = TimeUnit.NANOSECONDS.toMillis(closedAt.get() - startDiscard); assertTrue(discardMs >= 600, "Discard should reflect slow close path (took " + discardMs + "ms)"); pool.close(CloseMode.IMMEDIATE); } @Test void getRoutesCoversAllocatedAvailableAndWaiters() throws Exception { final RouteSegmentedConnPool pool = newPool(1, 1, TimeValue.NEG_ONE_MILLISECOND, PoolReusePolicy.LIFO, FakeConnection::close); assertTrue(pool.getRoutes().isEmpty(), "Initially there should be no routes"); // Allocate on rA final PoolEntry a = pool.lease("rA", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); assertEquals(new HashSet(Collections.singletonList("rA")), pool.getRoutes(), "rA must be listed because it is leased (allocated > 0)"); // Make rA available a.assignConnection(new FakeConnection()); a.updateExpiry(TimeValue.ofSeconds(30)); pool.release(a, true); assertEquals(new HashSet<>(Collections.singletonList("rA")), pool.getRoutes(), "rA must be listed because it has AVAILABLE entries"); // Enqueue waiter on rB (will time out) final Future> waiterB = pool.lease("rB", null, Timeout.ofMilliseconds(300), null); final Set routesNow = pool.getRoutes(); assertTrue(routesNow.contains("rA") && routesNow.contains("rB"), "Both rA (available) and rB (waiter) must be listed"); // Let rB time out (do NOT free capacity before the timeout fires) final ExecutionException ex = assertThrows( ExecutionException.class, () -> waiterB.get(600, TimeUnit.MILLISECONDS)); assertInstanceOf(TimeoutException.class, ex.getCause()); assertEquals("Lease timed out", ex.getCause().getMessage()); // Now drain rA by leasing and discarding to trigger segment cleanup final PoolEntry a2 = pool.lease("rA", null, Timeout.ofSeconds(1), null).get(1, TimeUnit.SECONDS); pool.release(a2, false); // discard final Set afterDropA = pool.getRoutes(); assertFalse(afterDropA.contains("rA"), "rA segment should be cleaned up"); assertFalse(afterDropA.contains("rB"), "rB waiter timed out; should not remain listed"); // Final cleanup pool.close(CloseMode.IMMEDIATE); assertTrue(pool.getRoutes().isEmpty(), "All routes must be gone after close()"); } } httpcore5/src/test/java/org/apache/hc/core5/pool/TestLaxConnPool.java0100644 0000000 0000000 00000053305 00000250600 024464 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; import java.util.Collections; import java.util.Random; 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 java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; class TestLaxConnPool { @Test void testEmptyPool() { try (final LaxConnPool pool = new LaxConnPool<>(2)) { final PoolStats totals = pool.getTotalStats(); Assertions.assertEquals(0, totals.getAvailable()); Assertions.assertEquals(0, totals.getLeased()); Assertions.assertEquals(0, totals.getPending()); Assertions.assertEquals(0, totals.getMax()); Assertions.assertEquals(Collections.emptySet(), pool.getRoutes()); final PoolStats stats = pool.getStats("somehost"); Assertions.assertEquals(0, stats.getAvailable()); Assertions.assertEquals(0, stats.getLeased()); Assertions.assertEquals(0, stats.getPending()); Assertions.assertEquals(2, stats.getMax()); Assertions.assertEquals("[leased: 0][available: 0][pending: 0]", pool.toString()); } } @Test void testInvalidConstruction() { Assertions.assertThrows(IllegalArgumentException.class, () -> new LaxConnPool(-1)); } @Test void testLeaseRelease() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); final HttpConnection conn2 = Mockito.mock(HttpConnection.class); final HttpConnection conn3 = Mockito.mock(HttpConnection.class); try (final LaxConnPool pool = new LaxConnPool<>(2)) { final Future> future1 = pool.lease("somehost", null); final Future> future2 = pool.lease("somehost", null); final Future> future3 = pool.lease("otherhost", null); final PoolEntry entry1 = future1.get(); Assertions.assertNotNull(entry1); entry1.assignConnection(conn1); final PoolEntry entry2 = future2.get(); Assertions.assertNotNull(entry2); entry2.assignConnection(conn2); final PoolEntry entry3 = future3.get(); Assertions.assertNotNull(entry3); entry3.assignConnection(conn3); pool.release(entry1, true); pool.release(entry2, true); pool.release(entry3, false); Mockito.verify(conn1, Mockito.never()).close(ArgumentMatchers.any()); Mockito.verify(conn2, Mockito.never()).close(ArgumentMatchers.any()); Mockito.verify(conn3, Mockito.times(1)).close(CloseMode.GRACEFUL); final PoolStats totals = pool.getTotalStats(); Assertions.assertEquals(2, totals.getAvailable()); Assertions.assertEquals(0, totals.getLeased()); Assertions.assertEquals(0, totals.getPending()); } } @Test void testLeaseReleaseMultiThreaded() throws Exception { try (final LaxConnPool pool = new LaxConnPool<>(2)) { final int c = 10; final CountDownLatch latch = new CountDownLatch(c); final AtomicInteger n = new AtomicInteger(c + 100); final AtomicReference exRef = new AtomicReference<>(); final ExecutorService executorService = Executors.newFixedThreadPool(c); try { final Random rnd = new Random(); for (int i = 0; i < c; i++) { executorService.execute(() -> { try { while (n.decrementAndGet() > 0) { try { final Future> future = pool.lease("somehost", null); final PoolEntry poolEntry = future.get(1, TimeUnit.MINUTES); Thread.sleep(rnd.nextInt(1)); pool.release(poolEntry, false); } catch (final Exception ex) { Assertions.fail(ex.getMessage(), ex); } } } catch (final AssertionError ex) { exRef.compareAndSet(null, ex); } finally { latch.countDown(); } }); } Assertions.assertTrue(latch.await(5, TimeUnit.MINUTES)); } finally { executorService.shutdownNow(); } final AssertionError assertionError = exRef.get(); if (assertionError != null) { throw assertionError; } } } @Test void testLeaseInvalid() { try (final LaxConnPool pool = new LaxConnPool<>(2)) { Assertions.assertThrows(NullPointerException.class, () -> pool.lease(null, null, Timeout.ZERO_MILLISECONDS, null)); } } @Test void testReleaseUnknownEntry() { try (final LaxConnPool pool = new LaxConnPool<>(2)) { Assertions.assertThrows(IllegalStateException.class, () -> pool.release(new PoolEntry<>("somehost"), true)); } } @Test void testMaxLimits() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); final HttpConnection conn2 = Mockito.mock(HttpConnection.class); final HttpConnection conn3 = Mockito.mock(HttpConnection.class); try (final LaxConnPool pool = new LaxConnPool<>(2)) { pool.setMaxPerRoute("somehost", 2); pool.setMaxPerRoute("otherhost", 1); final Future> future1 = pool.lease("somehost", null); final Future> future2 = pool.lease("somehost", null); final Future> future3 = pool.lease("otherhost", null); final PoolEntry entry1 = future1.get(); Assertions.assertNotNull(entry1); entry1.assignConnection(conn1); final PoolEntry entry2 = future2.get(); Assertions.assertNotNull(entry2); entry2.assignConnection(conn2); final PoolEntry entry3 = future3.get(); Assertions.assertNotNull(entry3); entry3.assignConnection(conn3); pool.release(entry1, true); pool.release(entry2, true); pool.release(entry3, true); final PoolStats totals = pool.getTotalStats(); Assertions.assertEquals(3, totals.getAvailable()); Assertions.assertEquals(0, totals.getLeased()); Assertions.assertEquals(0, totals.getPending()); final Future> future4 = pool.lease("somehost", null); final Future> future5 = pool.lease("somehost", null); final Future> future6 = pool.lease("otherhost", null); final Future> future7 = pool.lease("somehost", null); final Future> future8 = pool.lease("somehost", null); final Future> future9 = pool.lease("otherhost", null); Assertions.assertTrue(future4.isDone()); final PoolEntry entry4 = future4.get(); Assertions.assertNotNull(entry4); Assertions.assertSame(conn2, entry4.getConnection()); Assertions.assertTrue(future5.isDone()); final PoolEntry entry5 = future5.get(); Assertions.assertNotNull(entry5); Assertions.assertSame(conn1, entry5.getConnection()); Assertions.assertTrue(future6.isDone()); final PoolEntry entry6 = future6.get(); Assertions.assertNotNull(entry6); Assertions.assertSame(conn3, entry6.getConnection()); Assertions.assertFalse(future7.isDone()); Assertions.assertFalse(future8.isDone()); Assertions.assertFalse(future9.isDone()); pool.release(entry4, true); pool.release(entry5, false); pool.release(entry6, true); Assertions.assertTrue(future7.isDone()); final PoolEntry entry7 = future7.get(); Assertions.assertNotNull(entry7); Assertions.assertSame(conn2, entry7.getConnection()); Assertions.assertTrue(future8.isDone()); final PoolEntry entry8 = future8.get(); Assertions.assertNotNull(entry8); Assertions.assertNull(entry8.getConnection()); Assertions.assertTrue(future9.isDone()); final PoolEntry entry9 = future9.get(); Assertions.assertNotNull(entry9); Assertions.assertSame(conn3, entry9.getConnection()); } } @Test void testCreateNewIfExpired() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); try (final LaxConnPool pool = new LaxConnPool<>(2)) { final Future> future1 = pool.lease("somehost", null); Assertions.assertTrue(future1.isDone()); final PoolEntry entry1 = future1.get(); Assertions.assertNotNull(entry1); entry1.assignConnection(conn1); entry1.updateExpiry(TimeValue.of(1, TimeUnit.MILLISECONDS)); pool.release(entry1, true); Thread.sleep(200L); final Future> future2 = pool.lease("somehost", null); Assertions.assertTrue(future2.isDone()); Mockito.verify(conn1).close(CloseMode.GRACEFUL); final PoolStats totals = pool.getTotalStats(); Assertions.assertEquals(0, totals.getAvailable()); Assertions.assertEquals(1, totals.getLeased()); Assertions.assertEquals(Collections.singleton("somehost"), pool.getRoutes()); final PoolStats stats = pool.getStats("somehost"); Assertions.assertEquals(0, stats.getAvailable()); Assertions.assertEquals(1, stats.getLeased()); } } @Test void testCloseExpired() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); final HttpConnection conn2 = Mockito.mock(HttpConnection.class); try (final LaxConnPool pool = new LaxConnPool<>(2)) { final Future> future1 = pool.lease("somehost", null); final Future> future2 = pool.lease("somehost", null); Assertions.assertTrue(future1.isDone()); final PoolEntry entry1 = future1.get(); Assertions.assertNotNull(entry1); entry1.assignConnection(conn1); Assertions.assertTrue(future2.isDone()); final PoolEntry entry2 = future2.get(); Assertions.assertNotNull(entry2); entry2.assignConnection(conn2); entry1.updateExpiry(TimeValue.of(1, TimeUnit.MILLISECONDS)); pool.release(entry1, true); Thread.sleep(200); entry2.updateExpiry(TimeValue.of(1000, TimeUnit.SECONDS)); pool.release(entry2, true); pool.closeExpired(); Mockito.verify(conn1).close(CloseMode.GRACEFUL); Mockito.verify(conn2, Mockito.never()).close(ArgumentMatchers.any()); final PoolStats totals = pool.getTotalStats(); Assertions.assertEquals(1, totals.getAvailable()); Assertions.assertEquals(0, totals.getLeased()); Assertions.assertEquals(0, totals.getPending()); final PoolStats stats = pool.getStats("somehost"); Assertions.assertEquals(1, stats.getAvailable()); Assertions.assertEquals(0, stats.getLeased()); Assertions.assertEquals(0, stats.getPending()); } } @Test void testCloseIdle() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); final HttpConnection conn2 = Mockito.mock(HttpConnection.class); try (final LaxConnPool pool = new LaxConnPool<>(2)) { final Future> future1 = pool.lease("somehost", null); final Future> future2 = pool.lease("somehost", null); Assertions.assertTrue(future1.isDone()); final PoolEntry entry1 = future1.get(); Assertions.assertNotNull(entry1); entry1.assignConnection(conn1); Assertions.assertTrue(future2.isDone()); final PoolEntry entry2 = future2.get(); Assertions.assertNotNull(entry2); entry2.assignConnection(conn2); entry1.updateState(null); pool.release(entry1, true); Thread.sleep(200L); entry2.updateState(null); pool.release(entry2, true); pool.closeIdle(TimeValue.of(50, TimeUnit.MILLISECONDS)); Mockito.verify(conn1).close(CloseMode.GRACEFUL); Mockito.verify(conn2, Mockito.never()).close(ArgumentMatchers.any()); PoolStats totals = pool.getTotalStats(); Assertions.assertEquals(1, totals.getAvailable()); Assertions.assertEquals(0, totals.getLeased()); Assertions.assertEquals(0, totals.getPending()); PoolStats stats = pool.getStats("somehost"); Assertions.assertEquals(1, stats.getAvailable()); Assertions.assertEquals(0, stats.getLeased()); Assertions.assertEquals(0, stats.getPending()); pool.closeIdle(TimeValue.of(-1, TimeUnit.MILLISECONDS)); Mockito.verify(conn2).close(CloseMode.GRACEFUL); totals = pool.getTotalStats(); Assertions.assertEquals(0, totals.getAvailable()); Assertions.assertEquals(0, totals.getLeased()); Assertions.assertEquals(0, totals.getPending()); stats = pool.getStats("somehost"); Assertions.assertEquals(0, stats.getAvailable()); Assertions.assertEquals(0, stats.getLeased()); Assertions.assertEquals(0, stats.getPending()); Assertions.assertFalse(pool.isShutdown()); } } @Test void testLeaseRequestTimeout() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); try (final LaxConnPool pool = new LaxConnPool<>(1)) { final Future> future1 = pool.lease("somehost", null, Timeout.ofMilliseconds(0), null); final Future> future2 = pool.lease("somehost", null, Timeout.ofMilliseconds(0), null); final Future> future3 = pool.lease("somehost", null, Timeout.ofMilliseconds(10), null); Assertions.assertTrue(future1.isDone()); final PoolEntry entry1 = future1.get(); Assertions.assertNotNull(entry1); entry1.assignConnection(conn1); Assertions.assertFalse(future2.isDone()); Assertions.assertFalse(future3.isDone()); Thread.sleep(100); pool.validatePendingRequests(); Assertions.assertFalse(future2.isDone()); Assertions.assertTrue(future3.isDone()); } } @Test void testLeaseRequestCanceled() throws Exception { try (final LaxConnPool pool = new LaxConnPool<>(1)) { final Future> future1 = pool.lease("somehost", null, Timeout.ofMilliseconds(0), null); Assertions.assertTrue(future1.isDone()); final PoolEntry entry1 = future1.get(); Assertions.assertNotNull(entry1); entry1.assignConnection(Mockito.mock(HttpConnection.class)); final Future> future2 = pool.lease("somehost", null, Timeout.ofMilliseconds(0), null); future2.cancel(true); pool.release(entry1, true); final PoolStats totals = pool.getTotalStats(); Assertions.assertEquals(1, totals.getAvailable()); Assertions.assertEquals(0, totals.getLeased()); } } @Test void testGetStatsInvalid() { try (final LaxConnPool pool = new LaxConnPool<>(2)) { Assertions.assertThrows(NullPointerException.class, () -> pool.getStats(null)); } } @Test void testSetMaxInvalid() { try (final LaxConnPool pool = new LaxConnPool<>(2)) { Assertions.assertThrows(NullPointerException.class, () -> pool.setMaxPerRoute(null, 1)); Assertions.assertThrows(IllegalArgumentException.class, () -> pool.setDefaultMaxPerRoute(-1)); } } @Test void testShutdown() { final LaxConnPool pool = new LaxConnPool<>(2); pool.close(CloseMode.GRACEFUL); Assertions.assertThrows(IllegalStateException.class, () -> pool.lease("somehost", null)); // Ignored if shut down pool.release(new PoolEntry<>("somehost"), true); } @Test void testClose() { final LaxConnPool pool = new LaxConnPool<>(2); pool.setMaxPerRoute("someRoute", 2); pool.close(); Assertions.assertThrows(IllegalStateException.class, () -> pool.lease("someHost", null)); // Ignored if shut down pool.release(new PoolEntry<>("someHost"), true); } @Test void testGetMaxPerRoute() { final String route = "someRoute"; final int max = 2; try (final LaxConnPool pool = new LaxConnPool<>(2)) { pool.setMaxPerRoute(route, max); Assertions.assertEquals(max, pool.getMaxPerRoute(route)); } } @Test void testExpiredEntryDeallocatesSlot() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); try (final LaxConnPool pool = new LaxConnPool<>(1)) { // Lease, assign, expire, then release back to available final Future> future1 = pool.lease("somehost", null); final PoolEntry entry1 = future1.get(); entry1.assignConnection(conn1); entry1.updateExpiry(TimeValue.of(1, TimeUnit.MILLISECONDS)); pool.release(entry1, true); Thread.sleep(10); // Lease again — the expired entry should be discarded and the slot freed final Future> future2 = pool.lease("somehost", null); Assertions.assertTrue(future2.isDone()); final PoolEntry entry2 = future2.get(); Assertions.assertNotNull(entry2); // The expired entry was discarded, so a new entry was allocated. // With maxPerRoute=1, this only works if the expired entry's slot was deallocated. final PoolStats stats = pool.getStats("somehost"); Assertions.assertEquals(1, stats.getLeased()); Assertions.assertEquals(0, stats.getAvailable()); } } } httpcore5/src/test/java/org/apache/hc/core5/pool/TestPoolEntry.java0100644 0000000 0000000 00000013014 00000250600 024214 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.util.Deadline; import org.apache.hc.core5.util.TimeValue; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; class TestPoolEntry { private AtomicLong count; private Supplier currentTimeSupplier; @BeforeEach void setup() { count = new AtomicLong(1); currentTimeSupplier = () -> count.addAndGet(1); } @Test void testBasics() { final PoolEntry entry1 = new PoolEntry<>( "route1", TimeValue.of(10L, TimeUnit.MILLISECONDS), currentTimeSupplier); Assertions.assertEquals("route1", entry1.getRoute()); Assertions.assertEquals(0, entry1.getUpdated()); Assertions.assertEquals(Deadline.MIN_VALUE, entry1.getExpiryDeadline()); entry1.assignConnection(Mockito.mock(HttpConnection.class)); final long now = System.currentTimeMillis(); Assertions.assertEquals("route1", entry1.getRoute()); Assertions.assertTrue(now >= entry1.getUpdated()); Assertions.assertEquals(entry1.getValidityDeadline(), entry1.getExpiryDeadline()); Assertions.assertEquals(entry1.getUpdated() + 10L, entry1.getValidityDeadline().getValue()); entry1.discardConnection(CloseMode.IMMEDIATE); Assertions.assertEquals(0, entry1.getUpdated()); Assertions.assertEquals(Deadline.MIN_VALUE, entry1.getExpiryDeadline()); } @Test void testNullConstructor() { Assertions.assertThrows(NullPointerException.class, () -> new PoolEntry(null)); } @Test void testValidInfinitely() { final PoolEntry entry1 = new PoolEntry<>( "route1", TimeValue.ZERO_MILLISECONDS, currentTimeSupplier); entry1.assignConnection(Mockito.mock(HttpConnection.class)); Assertions.assertEquals(Deadline.MAX_VALUE, entry1.getValidityDeadline()); Assertions.assertEquals(entry1.getValidityDeadline(), entry1.getExpiryDeadline()); } @Test void testExpiry() { final PoolEntry entry1 = new PoolEntry<>( "route1", TimeValue.ZERO_MILLISECONDS, currentTimeSupplier); entry1.assignConnection(Mockito.mock(HttpConnection.class)); Assertions.assertEquals(Deadline.MAX_VALUE, entry1.getExpiryDeadline()); entry1.updateExpiry(TimeValue.of(50L, TimeUnit.MILLISECONDS)); Assertions.assertEquals(entry1.getUpdated() + 50L, entry1.getExpiryDeadline().getValue()); entry1.updateExpiry(TimeValue.ZERO_MILLISECONDS); Assertions.assertEquals(Deadline.MAX_VALUE, entry1.getExpiryDeadline()); final PoolEntry entry2 = new PoolEntry<>( "route1", TimeValue.of(100L, TimeUnit.MILLISECONDS), currentTimeSupplier); entry2.assignConnection(Mockito.mock(HttpConnection.class)); final Deadline validityDeadline = entry2.getValidityDeadline(); Assertions.assertEquals(entry2.getUpdated() + 100L, entry2.getExpiryDeadline().getValue()); entry2.updateExpiry(TimeValue.of(50L, TimeUnit.MILLISECONDS)); Assertions.assertEquals(entry2.getUpdated() + 50L, entry2.getExpiryDeadline().getValue()); entry2.updateExpiry(TimeValue.of(150L, TimeUnit.MILLISECONDS)); Assertions.assertEquals(validityDeadline, entry2.getExpiryDeadline()); } @Test void testInvalidExpiry() { final PoolEntry entry = new PoolEntry<>( "route1", TimeValue.of(0L, TimeUnit.MILLISECONDS), currentTimeSupplier); Assertions.assertThrows(NullPointerException.class, () -> entry.updateExpiry(null)); } @Test void testExpiryDoesNotOverflow() { final PoolEntry entry = new PoolEntry<>( "route1", TimeValue.of(Long.MAX_VALUE, TimeUnit.MILLISECONDS), currentTimeSupplier); entry.assignConnection(Mockito.mock(HttpConnection.class)); Assertions.assertEquals(Deadline.MAX_VALUE, entry.getValidityDeadline()); } } httpcore5/src/test/java/org/apache/hc/core5/pool/TestStrictConnPool.java0100644 0000000 0000000 00000074562 00000250600 025220 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; import java.util.Collections; import java.util.Random; import java.util.concurrent.CancellationException; 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.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.util.DeadlineTimeoutException; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; class TestStrictConnPool { @Test void testEmptyPool() { try (final StrictConnPool pool = new StrictConnPool<>(2, 10)) { final PoolStats totals = pool.getTotalStats(); Assertions.assertEquals(0, totals.getAvailable()); Assertions.assertEquals(0, totals.getLeased()); Assertions.assertEquals(0, totals.getPending()); Assertions.assertEquals(10, totals.getMax()); Assertions.assertEquals(Collections.emptySet(), pool.getRoutes()); final PoolStats stats = pool.getStats("somehost"); Assertions.assertEquals(0, stats.getAvailable()); Assertions.assertEquals(0, stats.getLeased()); Assertions.assertEquals(0, stats.getPending()); Assertions.assertEquals(2, stats.getMax()); Assertions.assertEquals("[leased: 0][available: 0][pending: 0]", pool.toString()); } } @Test void testInvalidConstruction() { Assertions.assertThrows(IllegalArgumentException.class, () -> new StrictConnPool(-1, 1)); Assertions.assertThrows(IllegalArgumentException.class, () -> new StrictConnPool(1, -1)); } @Test void testLeaseRelease() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); final HttpConnection conn2 = Mockito.mock(HttpConnection.class); final HttpConnection conn3 = Mockito.mock(HttpConnection.class); try (final StrictConnPool pool = new StrictConnPool<>(2, 10)) { final Future> future1 = pool.lease("somehost", null); final Future> future2 = pool.lease("somehost", null); final Future> future3 = pool.lease("otherhost", null); final PoolEntry entry1 = future1.get(); Assertions.assertNotNull(entry1); entry1.assignConnection(conn1); final PoolEntry entry2 = future2.get(); Assertions.assertNotNull(entry2); entry2.assignConnection(conn2); final PoolEntry entry3 = future3.get(); Assertions.assertNotNull(entry3); entry3.assignConnection(conn3); pool.release(entry1, true); pool.release(entry2, true); pool.release(entry3, false); Mockito.verify(conn1, Mockito.never()).close(ArgumentMatchers.any()); Mockito.verify(conn2, Mockito.never()).close(ArgumentMatchers.any()); Mockito.verify(conn3, Mockito.times(1)).close(CloseMode.GRACEFUL); final PoolStats totals = pool.getTotalStats(); Assertions.assertEquals(2, totals.getAvailable()); Assertions.assertEquals(0, totals.getLeased()); Assertions.assertEquals(0, totals.getPending()); } } @Test void testLeaseReleaseMultiThreaded() throws Exception { try (final StrictConnPool pool = new StrictConnPool<>(2, 10)) { final int c = 10; final CountDownLatch latch = new CountDownLatch(c); final AtomicInteger n = new AtomicInteger(c + 100); final AtomicReference exRef = new AtomicReference<>(); final ExecutorService executorService = Executors.newFixedThreadPool(c); try { final Random rnd = new Random(); for (int i = 0; i < c; i++) { executorService.execute(() -> { try { while (n.decrementAndGet() > 0) { try { final Future> future = pool.lease("somehost", null); final PoolEntry poolEntry = future.get(1, TimeUnit.MINUTES); Thread.sleep(rnd.nextInt(1)); pool.release(poolEntry, false); } catch (final Exception ex) { Assertions.fail(ex.getMessage(), ex); } } } catch (final AssertionError ex) { exRef.compareAndSet(null, ex); } finally { latch.countDown(); } }); } Assertions.assertTrue(latch.await(5, TimeUnit.MINUTES)); } finally { executorService.shutdownNow(); } final AssertionError assertionError = exRef.get(); if (assertionError != null) { throw assertionError; } } } @Test void testLeaseInvalid() { try (final StrictConnPool pool = new StrictConnPool<>(2, 10)) { Assertions.assertThrows(NullPointerException.class, () -> pool.lease(null, null, Timeout.ZERO_MILLISECONDS, null)); Assertions.assertThrows(NullPointerException.class, () -> pool.lease("somehost", null, null, null)); } } @Test void testReleaseUnknownEntry() { try (final StrictConnPool pool = new StrictConnPool<>(2, 2)) { Assertions.assertThrows(IllegalStateException.class, () -> pool.release(new PoolEntry<>("somehost"), true)); } } @Test void testMaxLimits() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); final HttpConnection conn2 = Mockito.mock(HttpConnection.class); final HttpConnection conn3 = Mockito.mock(HttpConnection.class); try (final StrictConnPool pool = new StrictConnPool<>(2, 10)) { pool.setMaxPerRoute("somehost", 2); pool.setMaxPerRoute("otherhost", 1); pool.setMaxTotal(3); final Future> future1 = pool.lease("somehost", null); final Future> future2 = pool.lease("somehost", null); final Future> future3 = pool.lease("otherhost", null); final PoolEntry entry1 = future1.get(); Assertions.assertNotNull(entry1); entry1.assignConnection(conn1); final PoolEntry entry2 = future2.get(); Assertions.assertNotNull(entry2); entry2.assignConnection(conn2); final PoolEntry entry3 = future3.get(); Assertions.assertNotNull(entry3); entry3.assignConnection(conn3); pool.release(entry1, true); pool.release(entry2, true); pool.release(entry3, true); final PoolStats totals = pool.getTotalStats(); Assertions.assertEquals(3, totals.getAvailable()); Assertions.assertEquals(0, totals.getLeased()); Assertions.assertEquals(0, totals.getPending()); final Future> future4 = pool.lease("somehost", null); final Future> future5 = pool.lease("somehost", null); final Future> future6 = pool.lease("otherhost", null); final Future> future7 = pool.lease("somehost", null); final Future> future8 = pool.lease("somehost", null); final Future> future9 = pool.lease("otherhost", null); Assertions.assertTrue(future4.isDone()); final PoolEntry entry4 = future4.get(); Assertions.assertNotNull(entry4); Assertions.assertSame(conn2, entry4.getConnection()); Assertions.assertTrue(future5.isDone()); final PoolEntry entry5 = future5.get(); Assertions.assertNotNull(entry5); Assertions.assertSame(conn1, entry5.getConnection()); Assertions.assertTrue(future6.isDone()); final PoolEntry entry6 = future6.get(); Assertions.assertNotNull(entry6); Assertions.assertSame(conn3, entry6.getConnection()); Assertions.assertFalse(future7.isDone()); Assertions.assertFalse(future8.isDone()); Assertions.assertFalse(future9.isDone()); pool.release(entry4, true); pool.release(entry5, false); pool.release(entry6, true); Assertions.assertTrue(future7.isDone()); final PoolEntry entry7 = future7.get(); Assertions.assertNotNull(entry7); Assertions.assertSame(conn2, entry7.getConnection()); Assertions.assertTrue(future8.isDone()); final PoolEntry entry8 = future8.get(); Assertions.assertNotNull(entry8); Assertions.assertNull(entry8.getConnection()); Assertions.assertTrue(future9.isDone()); final PoolEntry entry9 = future9.get(); Assertions.assertNotNull(entry9); Assertions.assertSame(conn3, entry9.getConnection()); } } @Test void testConnectionRedistributionOnTotalMaxLimit() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); final HttpConnection conn2 = Mockito.mock(HttpConnection.class); final HttpConnection conn3 = Mockito.mock(HttpConnection.class); final HttpConnection conn4 = Mockito.mock(HttpConnection.class); final HttpConnection conn5 = Mockito.mock(HttpConnection.class); try (final StrictConnPool pool = new StrictConnPool<>(2, 10)) { pool.setMaxPerRoute("somehost", 2); pool.setMaxPerRoute("otherhost", 2); pool.setMaxTotal(2); final Future> future1 = pool.lease("somehost", null); final Future> future2 = pool.lease("somehost", null); final Future> future3 = pool.lease("otherhost", null); final Future> future4 = pool.lease("otherhost", null); Assertions.assertTrue(future1.isDone()); final PoolEntry entry1 = future1.get(); Assertions.assertNotNull(entry1); Assertions.assertFalse(entry1.hasConnection()); entry1.assignConnection(conn1); Assertions.assertTrue(future2.isDone()); final PoolEntry entry2 = future2.get(); Assertions.assertNotNull(entry2); Assertions.assertFalse(entry2.hasConnection()); entry2.assignConnection(conn2); Assertions.assertFalse(future3.isDone()); Assertions.assertFalse(future4.isDone()); PoolStats totals = pool.getTotalStats(); Assertions.assertEquals(0, totals.getAvailable()); Assertions.assertEquals(2, totals.getLeased()); Assertions.assertEquals(2, totals.getPending()); pool.release(entry1, true); pool.release(entry2, true); Assertions.assertTrue(future3.isDone()); final PoolEntry entry3 = future3.get(); Assertions.assertNotNull(entry3); Assertions.assertFalse(entry3.hasConnection()); entry3.assignConnection(conn3); Assertions.assertTrue(future4.isDone()); final PoolEntry entry4 = future4.get(); Assertions.assertNotNull(entry4); Assertions.assertFalse(entry4.hasConnection()); entry4.assignConnection(conn4); totals = pool.getTotalStats(); Assertions.assertEquals(0, totals.getAvailable()); Assertions.assertEquals(2, totals.getLeased()); Assertions.assertEquals(0, totals.getPending()); final Future> future5 = pool.lease("somehost", null); final Future> future6 = pool.lease("otherhost", null); pool.release(entry3, true); pool.release(entry4, true); Assertions.assertTrue(future5.isDone()); final PoolEntry entry5 = future5.get(); Assertions.assertNotNull(entry5); Assertions.assertFalse(entry5.hasConnection()); entry5.assignConnection(conn5); Assertions.assertTrue(future6.isDone()); final PoolEntry entry6 = future6.get(); Assertions.assertNotNull(entry6); Assertions.assertTrue(entry6.hasConnection()); Assertions.assertSame(conn4, entry6.getConnection()); totals = pool.getTotalStats(); Assertions.assertEquals(0, totals.getAvailable()); Assertions.assertEquals(2, totals.getLeased()); Assertions.assertEquals(0, totals.getPending()); pool.release(entry5, true); pool.release(entry6, true); totals = pool.getTotalStats(); Assertions.assertEquals(2, totals.getAvailable()); Assertions.assertEquals(0, totals.getLeased()); Assertions.assertEquals(0, totals.getPending()); } } @Test void testStatefulConnectionRedistributionOnPerRouteMaxLimit() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); final HttpConnection conn2 = Mockito.mock(HttpConnection.class); try (final StrictConnPool pool = new StrictConnPool<>(2, 10)) { pool.setMaxPerRoute("somehost", 2); pool.setMaxTotal(2); final Future> future1 = pool.lease("somehost", null); final Future> future2 = pool.lease("somehost", null); Assertions.assertTrue(future1.isDone()); final PoolEntry entry1 = future1.get(); entry1.assignConnection(conn1); Assertions.assertNotNull(entry1); Assertions.assertTrue(future2.isDone()); final PoolEntry entry2 = future2.get(); Assertions.assertNotNull(entry2); entry2.assignConnection(conn2); PoolStats totals = pool.getTotalStats(); Assertions.assertEquals(0, totals.getAvailable()); Assertions.assertEquals(2, totals.getLeased()); Assertions.assertEquals(0, totals.getPending()); entry1.updateState("some-stuff"); pool.release(entry1, true); entry2.updateState("some-stuff"); pool.release(entry2, true); final Future> future3 = pool.lease("somehost", "some-stuff"); final Future> future4 = pool.lease("somehost", "some-stuff"); Assertions.assertTrue(future1.isDone()); final PoolEntry entry3 = future3.get(); Assertions.assertNotNull(entry3); Assertions.assertSame(conn2, entry3.getConnection()); Assertions.assertTrue(future4.isDone()); final PoolEntry entry4 = future4.get(); Assertions.assertNotNull(entry4); Assertions.assertSame(conn1, entry4.getConnection()); pool.release(entry3, true); pool.release(entry4, true); totals = pool.getTotalStats(); Assertions.assertEquals(2, totals.getAvailable()); Assertions.assertEquals(0, totals.getLeased()); Assertions.assertEquals(0, totals.getPending()); final Future> future5 = pool.lease("somehost", "some-other-stuff"); Assertions.assertTrue(future5.isDone()); Mockito.verify(conn2).close(CloseMode.GRACEFUL); Mockito.verify(conn1, Mockito.never()).close(ArgumentMatchers.any()); totals = pool.getTotalStats(); Assertions.assertEquals(1, totals.getAvailable()); Assertions.assertEquals(1, totals.getLeased()); } } @Test void testCreateNewIfExpired() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); try (final StrictConnPool pool = new StrictConnPool<>(2, 2)) { final Future> future1 = pool.lease("somehost", null); Assertions.assertTrue(future1.isDone()); final PoolEntry entry1 = future1.get(); Assertions.assertNotNull(entry1); entry1.assignConnection(conn1); entry1.updateExpiry(TimeValue.of(1, TimeUnit.MILLISECONDS)); pool.release(entry1, true); Thread.sleep(200L); final Future> future2 = pool.lease("somehost", null); Assertions.assertTrue(future2.isDone()); Mockito.verify(conn1).close(CloseMode.GRACEFUL); final PoolStats totals = pool.getTotalStats(); Assertions.assertEquals(0, totals.getAvailable()); Assertions.assertEquals(1, totals.getLeased()); Assertions.assertEquals(Collections.singleton("somehost"), pool.getRoutes()); final PoolStats stats = pool.getStats("somehost"); Assertions.assertEquals(0, stats.getAvailable()); Assertions.assertEquals(1, stats.getLeased()); } } @Test void testCloseExpired() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); final HttpConnection conn2 = Mockito.mock(HttpConnection.class); try (final StrictConnPool pool = new StrictConnPool<>(2, 2)) { final Future> future1 = pool.lease("somehost", null); final Future> future2 = pool.lease("somehost", null); Assertions.assertTrue(future1.isDone()); final PoolEntry entry1 = future1.get(); Assertions.assertNotNull(entry1); entry1.assignConnection(conn1); Assertions.assertTrue(future2.isDone()); final PoolEntry entry2 = future2.get(); Assertions.assertNotNull(entry2); entry2.assignConnection(conn2); entry1.updateExpiry(TimeValue.of(1, TimeUnit.MILLISECONDS)); pool.release(entry1, true); Thread.sleep(200); entry2.updateExpiry(TimeValue.of(1000, TimeUnit.SECONDS)); pool.release(entry2, true); pool.closeExpired(); Mockito.verify(conn1).close(CloseMode.GRACEFUL); Mockito.verify(conn2, Mockito.never()).close(ArgumentMatchers.any()); final PoolStats totals = pool.getTotalStats(); Assertions.assertEquals(1, totals.getAvailable()); Assertions.assertEquals(0, totals.getLeased()); Assertions.assertEquals(0, totals.getPending()); final PoolStats stats = pool.getStats("somehost"); Assertions.assertEquals(1, stats.getAvailable()); Assertions.assertEquals(0, stats.getLeased()); Assertions.assertEquals(0, stats.getPending()); } } @Test void testCloseIdle() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); final HttpConnection conn2 = Mockito.mock(HttpConnection.class); try (final StrictConnPool pool = new StrictConnPool<>(2, 2)) { final Future> future1 = pool.lease("somehost", null); final Future> future2 = pool.lease("somehost", null); Assertions.assertTrue(future1.isDone()); final PoolEntry entry1 = future1.get(); Assertions.assertNotNull(entry1); entry1.assignConnection(conn1); Assertions.assertTrue(future2.isDone()); final PoolEntry entry2 = future2.get(); Assertions.assertNotNull(entry2); entry2.assignConnection(conn2); entry1.updateState(null); pool.release(entry1, true); Thread.sleep(200L); entry2.updateState(null); pool.release(entry2, true); pool.closeIdle(TimeValue.of(50, TimeUnit.MILLISECONDS)); Mockito.verify(conn1).close(CloseMode.GRACEFUL); Mockito.verify(conn2, Mockito.never()).close(ArgumentMatchers.any()); PoolStats totals = pool.getTotalStats(); Assertions.assertEquals(1, totals.getAvailable()); Assertions.assertEquals(0, totals.getLeased()); Assertions.assertEquals(0, totals.getPending()); PoolStats stats = pool.getStats("somehost"); Assertions.assertEquals(1, stats.getAvailable()); Assertions.assertEquals(0, stats.getLeased()); Assertions.assertEquals(0, stats.getPending()); pool.closeIdle(TimeValue.of(-1, TimeUnit.MILLISECONDS)); Mockito.verify(conn2).close(CloseMode.GRACEFUL); totals = pool.getTotalStats(); Assertions.assertEquals(0, totals.getAvailable()); Assertions.assertEquals(0, totals.getLeased()); Assertions.assertEquals(0, totals.getPending()); stats = pool.getStats("somehost"); Assertions.assertEquals(0, stats.getAvailable()); Assertions.assertEquals(0, stats.getLeased()); Assertions.assertEquals(0, stats.getPending()); } } @Test void testLeaseRequestTimeout() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); try (final StrictConnPool pool = new StrictConnPool<>(1, 1)) { final Future> future1 = pool.lease("somehost", null, Timeout.ofMilliseconds(0), null); final Future> future2 = pool.lease("somehost", null, Timeout.ofMilliseconds(0), null); final Future> future3 = pool.lease("somehost", null, Timeout.ofMilliseconds(10), null); Assertions.assertTrue(future1.isDone()); final PoolEntry entry1 = future1.get(); Assertions.assertNotNull(entry1); entry1.assignConnection(conn1); Assertions.assertFalse(future2.isDone()); Assertions.assertFalse(future3.isDone()); Thread.sleep(100); pool.validatePendingRequests(); Assertions.assertFalse(future2.isDone()); Assertions.assertTrue(future3.isDone()); } } private static class HoldInternalLockThread extends Thread { private HoldInternalLockThread(final StrictConnPool pool, final CountDownLatch lockHeld) { super(() -> { pool.lease("somehost", null); // lease a connection so we have something to enumLeased() pool.enumLeased(object -> { try { lockHeld.countDown(); Thread.sleep(Long.MAX_VALUE); } catch (final InterruptedException ignored) { } }); }); } } @Test void testLeaseRequestLockTimeout() throws Exception { final StrictConnPool pool = new StrictConnPool<>(1, 1); final CountDownLatch lockHeld = new CountDownLatch(1); final Thread holdInternalLock = new HoldInternalLockThread(pool, lockHeld); holdInternalLock.start(); // Start a thread to grab the internal conn pool lock lockHeld.await(); // Wait until we know the internal lock is held // Attempt to get a connection while lock is held final Future> future2 = pool.lease("somehost", null, Timeout.ofMilliseconds(10), null); final ExecutionException executionException = Assertions.assertThrows(ExecutionException.class, () -> future2.get()); Assertions.assertTrue(executionException.getCause() instanceof DeadlineTimeoutException); holdInternalLock.interrupt(); // Cleanup } @Test void testLeaseRequestInterrupted() throws Exception { final StrictConnPool pool = new StrictConnPool<>(1, 1); final CountDownLatch lockHeld = new CountDownLatch(1); final Thread holdInternalLock = new HoldInternalLockThread(pool, lockHeld); holdInternalLock.start(); // Start a thread to grab the internal conn pool lock lockHeld.await(); // Wait until we know the internal lock is held Thread.currentThread().interrupt(); // Attempt to get a connection while lock is held and thread is interrupted final Future> future2 = pool.lease("somehost", null, Timeout.ofMilliseconds(10), null); Assertions.assertTrue(Thread.interrupted()); Assertions.assertThrows(CancellationException.class, () -> future2.get()); holdInternalLock.interrupt(); // Cleanup } @Test void testLeaseRequestCanceled() throws Exception { try (final StrictConnPool pool = new StrictConnPool<>(1, 1)) { final Future> future1 = pool.lease("somehost", null, Timeout.ofMilliseconds(0), null); Assertions.assertTrue(future1.isDone()); final PoolEntry entry1 = future1.get(); Assertions.assertNotNull(entry1); entry1.assignConnection(Mockito.mock(HttpConnection.class)); final Future> future2 = pool.lease("somehost", null, Timeout.ofMilliseconds(0), null); future2.cancel(true); pool.release(entry1, true); final PoolStats totals = pool.getTotalStats(); Assertions.assertEquals(1, totals.getAvailable()); Assertions.assertEquals(0, totals.getLeased()); } } @Test void testGetStatsInvalid() { try (final StrictConnPool pool = new StrictConnPool<>(2, 2)) { Assertions.assertThrows(NullPointerException.class, () -> pool.getStats(null)); } } @Test void testSetMaxInvalid() { try (final StrictConnPool pool = new StrictConnPool<>(2, 2)) { Assertions.assertThrows(IllegalArgumentException.class, () -> pool.setMaxTotal(-1)); Assertions.assertThrows(NullPointerException.class, () -> pool.setMaxPerRoute(null, 1)); Assertions.assertThrows(IllegalArgumentException.class, () -> pool.setDefaultMaxPerRoute(-1)); } } @Test void testSetMaxPerRoute() { try (final StrictConnPool pool = new StrictConnPool<>(2, 2)) { pool.setMaxPerRoute("somehost", 1); Assertions.assertEquals(1, pool.getMaxPerRoute("somehost")); pool.setMaxPerRoute("somehost", 0); Assertions.assertEquals(0, pool.getMaxPerRoute("somehost")); pool.setMaxPerRoute("somehost", -1); Assertions.assertEquals(2, pool.getMaxPerRoute("somehost")); } } @Test void testShutdown() { final StrictConnPool pool = new StrictConnPool<>(2, 2); pool.close(CloseMode.GRACEFUL); Assertions.assertThrows(IllegalStateException.class, () -> pool.lease("somehost", null)); // Ignored if shut down pool.release(new PoolEntry<>("somehost"), true); } } httpcore5/src/test/java/org/apache/hc/core5/pool/TestStrictConnPoolLeaseTimeoutRace.java0100644 0000000 0000000 00000014234 00000250600 030322 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.pool; 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.Future; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import java.util.stream.Stream; import org.apache.hc.core5.http.SocketModalCloseable; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; class TestConnPoolLeaseTimeout { private static final Timeout TIMEOUT = Timeout.ofSeconds(30); static final class DummyConn implements SocketModalCloseable { private volatile Timeout socketTimeout; @Override public Timeout getSocketTimeout() { return socketTimeout; } @Override public void setSocketTimeout(final Timeout timeout) { this.socketTimeout = timeout; } @Override public void close(final CloseMode closeMode) { } @Override public void close() throws IOException { } } static final class PoolCase { final String name; final Supplier> supplier; PoolCase(final String name, final Supplier> supplier) { this.name = name; this.supplier = supplier; } @Override public String toString() { return name; } } static Stream pools() { return Stream.of( new PoolCase("STRICT", () -> new StrictConnPool<>(1, 1)), new PoolCase("LAX", () -> new LaxConnPool<>(1)), new PoolCase("OFFLOCK", () -> new RouteSegmentedConnPool<>( 1, 1, TimeValue.NEG_ONE_MILLISECOND, PoolReusePolicy.LIFO, new DefaultDisposalCallback<>())) ); } @ParameterizedTest(name = "{0}") @MethodSource("pools") @org.junit.jupiter.api.Timeout(60) void testLeaseTimeoutDoesNotLeakLeasedEntries(final PoolCase poolCase) throws Exception { final ManagedConnPool pool = poolCase.supplier.get(); final String route = "route-1"; final Timeout requestTimeout = Timeout.ofMicroseconds(1); final int concurrentThreads = 10; final CountDownLatch countDownLatch = new CountDownLatch(concurrentThreads); final AtomicLong n = new AtomicLong(concurrentThreads * 100); final ExecutorService executorService = Executors.newFixedThreadPool(concurrentThreads); final AtomicReference unexpectedException = new AtomicReference<>(); try { for (int i = 0; i < concurrentThreads; i++) { executorService.execute(() -> { try { while (n.decrementAndGet() > 0) { final Future> f = pool.lease(route, null, requestTimeout, null); try { final PoolEntry entry = f.get(requestTimeout.getDuration(), requestTimeout.getTimeUnit()); pool.release(entry, true); } catch (final InterruptedException ex) { Thread.currentThread().interrupt(); unexpectedException.compareAndSet(null, ex); } catch (final TimeoutException ex) { f.cancel(true); } catch (final ExecutionException ex) { f.cancel(true); if (!(ex.getCause() instanceof TimeoutException)) { unexpectedException.compareAndSet(null, ex); } } } } finally { countDownLatch.countDown(); } }); } Assertions.assertTrue(countDownLatch.await(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); Assertions.assertTrue(n.get() <= 0); Assertions.assertNull(unexpectedException.get()); final PoolStats stats = pool.getStats(route); Assertions.assertEquals(0, stats.getLeased()); } finally { executorService.shutdownNow(); executorService.awaitTermination(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); pool.close(CloseMode.GRACEFUL); } } }httpcore5/src/test/java/org/apache/hc/core5/reactor/0040755 0000000 0000000 00000000000 00000250600 021250 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/reactor/IOReactorConfigTest.java0100644 0000000 0000000 00000007154 00000250600 025734 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import java.net.InetSocketAddress; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class IOReactorConfigTest { @Test void testCustomIOReactorConfig() { final IOReactorConfig reactorConfig = IOReactorConfig.custom() .setSelectInterval(TimeValue.ofMilliseconds(500)) .setIoThreadCount(2) .setSoTimeout(Timeout.ofSeconds(10)) .setSoReuseAddress(true) .setSoLinger(TimeValue.ofSeconds(30)) .setSoKeepAlive(true) .setTcpNoDelay(false) .setTrafficClass(0x02) .setSndBufSize(32767) .setRcvBufSize(8192) .setBacklogSize(5) .setTcpKeepIdle(100) .setTcpKeepInterval(12) .setTcpKeepCount(4) .setSocksProxyAddress(new InetSocketAddress(8888)) .setSocksProxyUsername("socksProxyUsername") .setSocksProxyPassword("socksProxyPassword") .build(); Assertions.assertEquals(TimeValue.ofMilliseconds(500), reactorConfig.getSelectInterval()); Assertions.assertEquals(2, reactorConfig.getIoThreadCount()); Assertions.assertEquals(Timeout.ofSeconds(10), reactorConfig.getSoTimeout()); Assertions.assertTrue(reactorConfig.isSoReuseAddress()); Assertions.assertEquals(TimeValue.ofSeconds(30), reactorConfig.getSoLinger()); Assertions.assertTrue(reactorConfig.isSoKeepAlive()); Assertions.assertFalse(reactorConfig.isTcpNoDelay()); Assertions.assertEquals(0x02, reactorConfig.getTrafficClass()); Assertions.assertEquals(32767, reactorConfig.getSndBufSize()); Assertions.assertEquals(8192, reactorConfig.getRcvBufSize()); Assertions.assertEquals(5, reactorConfig.getBacklogSize()); Assertions.assertEquals(100, reactorConfig.getTcpKeepIdle()); Assertions.assertEquals(12, reactorConfig.getTcpKeepInterval()); Assertions.assertEquals(4, reactorConfig.getTcpKeepCount()); Assertions.assertEquals(new InetSocketAddress(8888), reactorConfig.getSocksProxyAddress()); Assertions.assertEquals("socksProxyUsername", reactorConfig.getSocksProxyUsername()); Assertions.assertEquals("socksProxyPassword", reactorConfig.getSocksProxyPassword()); } } httpcore5/src/test/java/org/apache/hc/core5/reactor/IOWorkerSelectorsTest.java0100644 0000000 0000000 00000004123 00000250600 026335 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mockito; class IOWorkerSelectorsTest { @ParameterizedTest(name = "worker count = {0}") @ValueSource(ints = {1, 2, 3, 4, 5, 10, 15, 16, 32}) void testIndexOverflow(final int workerCount) { final long start = (long) Integer.MAX_VALUE - 10; final long end = (long) Integer.MAX_VALUE + 10; final IOWorkerStats[] workers = new IOWorkerStats[workerCount]; for (int i = 0; i < workerCount; i++) { workers[i] = Mockito.mock(IOWorkerStats.class); } final IOWorkerSelector selector = IOWorkerSelectors.newSelector(workerCount, (int) start); for (long i = start; i < end; i++) { Assertions.assertTrue(selector.select(workers) < workerCount); } } } httpcore5/src/test/java/org/apache/hc/core5/reactor/TestAbstractIOSessionPool.java0100644 0000000 0000000 00000026313 00000250600 027146 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import static org.hamcrest.MatcherAssert.assertThat; import java.net.UnknownHostException; import java.util.concurrent.Future; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; class TestAbstractIOSessionPool { @Mock private Future connectFuture; @Mock private FutureCallback callback1; @Mock private FutureCallback callback2; @Mock private IOSession ioSession1; @Mock private IOSession ioSession2; @Captor ArgumentCaptor> connectCallbackCaptor; private AbstractIOSessionPool impl; @BeforeEach void setup() { MockitoAnnotations.openMocks(this); impl = Mockito.mock(AbstractIOSessionPool.class, Mockito.withSettings() .defaultAnswer(Answers.CALLS_REAL_METHODS) .useConstructor()); } @Test void testGetSessions() throws Exception { Mockito.when(impl.connectSession( ArgumentMatchers.anyString(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(connectFuture); Mockito.doAnswer(invocation -> { final Callback callback = invocation.getArgument(1); callback.execute(true); return null; }).when(impl).validateSession(ArgumentMatchers.any(), ArgumentMatchers.any()); Mockito.when(ioSession1.isOpen()).thenReturn(true); final Future future1 = impl.getSession("somehost", Timeout.ofSeconds(123L), null); assertThat(future1, CoreMatchers.notNullValue()); assertThat(future1.isDone(), CoreMatchers.equalTo(false)); assertThat(impl.getRoutes(), CoreMatchers.hasItem("somehost")); Mockito.verify(impl).connectSession( ArgumentMatchers.eq("somehost"), ArgumentMatchers.eq(Timeout.ofSeconds(123L)), ArgumentMatchers.any()); final Future future2 = impl.getSession("somehost", Timeout.ofSeconds(123L), null); assertThat(future2, CoreMatchers.notNullValue()); assertThat(future2.isDone(), CoreMatchers.equalTo(false)); assertThat(impl.getRoutes(), CoreMatchers.hasItem("somehost")); Mockito.verify(impl, Mockito.times(1)).connectSession( ArgumentMatchers.eq("somehost"), ArgumentMatchers.any(), ArgumentMatchers.argThat(callback -> { callback.completed(ioSession1); return true; })); assertThat(future1.isDone(), CoreMatchers.equalTo(true)); assertThat(future1.get(), CoreMatchers.sameInstance(ioSession1)); assertThat(future2.isDone(), CoreMatchers.equalTo(true)); assertThat(future2.get(), CoreMatchers.sameInstance(ioSession1)); Mockito.verify(impl, Mockito.times(2)).validateSession(ArgumentMatchers.any(), ArgumentMatchers.any()); final Future future3 = impl.getSession("somehost", Timeout.ofSeconds(123L), null); Mockito.verify(impl, Mockito.times(1)).connectSession( ArgumentMatchers.eq("somehost"), ArgumentMatchers.any(), ArgumentMatchers.any()); Mockito.verify(impl, Mockito.times(3)).validateSession(ArgumentMatchers.any(), ArgumentMatchers.any()); assertThat(future3.isDone(), CoreMatchers.equalTo(true)); assertThat(future3.get(), CoreMatchers.sameInstance(ioSession1)); } @Test void testGetSessionConnectFailure() { Mockito.when(impl.connectSession( ArgumentMatchers.anyString(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(connectFuture); final Future future1 = impl.getSession("somehost", Timeout.ofSeconds(123L), null); assertThat(future1, CoreMatchers.notNullValue()); assertThat(future1.isDone(), CoreMatchers.equalTo(false)); assertThat(impl.getRoutes(), CoreMatchers.hasItem("somehost")); Mockito.verify(impl).connectSession( ArgumentMatchers.eq("somehost"), ArgumentMatchers.eq(Timeout.ofSeconds(123L)), connectCallbackCaptor.capture()); final Future future2 = impl.getSession("somehost", Timeout.ofSeconds(123L), null); assertThat(future2, CoreMatchers.notNullValue()); assertThat(future2.isDone(), CoreMatchers.equalTo(false)); assertThat(impl.getRoutes(), CoreMatchers.hasItem("somehost")); final FutureCallback connectCallback = connectCallbackCaptor.getValue(); Assertions.assertNotNull(connectCallback); connectCallback.failed(new Exception("Boom")); // Ensure connect failure invalidates all pending futures assertThat(future1.isDone(), CoreMatchers.equalTo(true)); assertThat(future2.isDone(), CoreMatchers.equalTo(true)); } @Test void testShutdownPool() { final AbstractIOSessionPool.PoolEntry entry1 = impl.getPoolEntry("host1"); assertThat(entry1, CoreMatchers.notNullValue()); entry1.session = ioSession1; final AbstractIOSessionPool.PoolEntry entry2 = impl.getPoolEntry("host2"); assertThat(entry2, CoreMatchers.notNullValue()); entry2.session = ioSession2; final AbstractIOSessionPool.PoolEntry entry3 = impl.getPoolEntry("host3"); assertThat(entry3, CoreMatchers.notNullValue()); entry3.sessionFuture = connectFuture; entry3.requestQueue.add(callback1); entry3.requestQueue.add(callback2); impl.close(CloseMode.GRACEFUL); Mockito.verify(impl).closeSession(ioSession1, CloseMode.GRACEFUL); Mockito.verify(impl).closeSession(ioSession2, CloseMode.GRACEFUL); Mockito.verify(connectFuture).cancel(ArgumentMatchers.anyBoolean()); Mockito.verify(callback1).cancelled(); Mockito.verify(callback2).cancelled(); } @Test void testCloseIdleSessions() { final AbstractIOSessionPool.PoolEntry entry1 = impl.getPoolEntry("host1"); assertThat(entry1, CoreMatchers.notNullValue()); entry1.session = ioSession1; final AbstractIOSessionPool.PoolEntry entry2 = impl.getPoolEntry("host2"); assertThat(entry2, CoreMatchers.notNullValue()); entry2.session = ioSession2; impl.closeIdle(TimeValue.ZERO_MILLISECONDS); Mockito.verify(impl).closeSession(ioSession1, CloseMode.GRACEFUL); Mockito.verify(impl).closeSession(ioSession2, CloseMode.GRACEFUL); assertThat(entry1.session, CoreMatchers.nullValue()); assertThat(entry2.session, CoreMatchers.nullValue()); } @Test void testEnumSessions() { final AbstractIOSessionPool.PoolEntry entry1 = impl.getPoolEntry("host1"); assertThat(entry1, CoreMatchers.notNullValue()); entry1.session = ioSession1; final AbstractIOSessionPool.PoolEntry entry2 = impl.getPoolEntry("host2"); assertThat(entry2, CoreMatchers.notNullValue()); entry2.session = ioSession2; impl.enumAvailable(ioSession -> ioSession.close(CloseMode.GRACEFUL)); Mockito.verify(ioSession1).close(CloseMode.GRACEFUL); Mockito.verify(ioSession2).close(CloseMode.GRACEFUL); } @Test void testGetSessionReconnectAfterValidate() { final AbstractIOSessionPool.PoolEntry entry1 = impl.getPoolEntry("somehost"); assertThat(entry1, CoreMatchers.notNullValue()); entry1.session = ioSession1; Mockito.when(ioSession1.isOpen()).thenReturn(true); Mockito.doAnswer(invocation -> { final Callback callback = invocation.getArgument(1); callback.execute(false); return null; }).when(impl).validateSession(ArgumentMatchers.any(), ArgumentMatchers.any()); impl.getSession("somehost", Timeout.ofSeconds(123L), null); Mockito.verify(impl, Mockito.times(1)).connectSession( ArgumentMatchers.eq("somehost"), ArgumentMatchers.eq(Timeout.ofSeconds(123L)), ArgumentMatchers.any()); } @Test void testGetSessionReconnectIfClosed() { final AbstractIOSessionPool.PoolEntry entry1 = impl.getPoolEntry("somehost"); assertThat(entry1, CoreMatchers.notNullValue()); entry1.session = ioSession1; Mockito.when(ioSession1.isOpen()).thenReturn(false); impl.getSession("somehost", Timeout.ofSeconds(123L), null); Mockito.verify(impl).connectSession( ArgumentMatchers.eq("somehost"), ArgumentMatchers.eq(Timeout.ofSeconds(123L)), ArgumentMatchers.any()); } @Test void testGetSessionConnectUnknownHost() { Mockito.when(connectFuture.isDone()).thenReturn(true); Mockito.when(impl.connectSession( ArgumentMatchers.anyString(), ArgumentMatchers.any(), ArgumentMatchers.argThat(callback -> { callback.failed(new UnknownHostException("Boom")); return true; }))).thenReturn(connectFuture); final Future future1 = impl.getSession("somehost", Timeout.ofSeconds(123L), null); assertThat(future1, CoreMatchers.notNullValue()); assertThat(future1.isDone(), CoreMatchers.equalTo(true)); final Future future2 = impl.getSession("somehost", Timeout.ofSeconds(123L), null); assertThat(future2, CoreMatchers.notNullValue()); assertThat(future2.isDone(), CoreMatchers.equalTo(true)); } } httpcore5/src/test/java/org/apache/hc/core5/reactor/TestSocksProxyProtocolHandler.java0100644 0000000 0000000 00000022511 00000250600 030115 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.nio.channels.SelectionKey; import java.util.ArrayDeque; import java.util.Deque; import java.util.concurrent.ExecutionException; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.nio.command.CommandSupport; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Test; class TestSocksProxyProtocolHandler { @Test @org.junit.jupiter.api.Timeout(5) void socksProxyEOFDuringConnectResponseCompletesSessionRequestExceptionally() throws Exception { final IOReactorConfig reactorConfig = IOReactorConfig.custom().build(); final NamedEndpoint remoteEndpoint = new NamedEndpoint() { @Override public String getHostName() { return "example"; } @Override public int getPort() { return 443; } }; final SocketAddress targetAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), 443); final IOSessionRequest sessionRequest = new IOSessionRequest( remoteEndpoint, targetAddress, null, Timeout.ofSeconds(1), null, null); // dataChannel + eventHandlerFactory are only used on COMPLETE; we never reach that in this regression. final SocksProxyProtocolHandler handler = new SocksProxyProtocolHandler(null, sessionRequest, null, reactorConfig); final TestIOSession session = new TestIOSession(); assertEquals(0, session.getEventMask()); // 1) Client sends auth methods handler.connected(session); assertEquals(SelectionKey.OP_WRITE, session.getEventMask()); handler.outputReady(session); assertEquals(SelectionKey.OP_READ, session.getEventMask()); // 2) Server replies: VER=5, METHOD=NO_AUTH(0) handler.inputReady(session, ByteBuffer.wrap(new byte[]{0x05, 0x00})); assertEquals(SelectionKey.OP_WRITE, session.getEventMask()); // 3) Client sends CONNECT, then expects 2 bytes: VER + REP handler.outputReady(session); assertEquals(SelectionKey.OP_READ, session.getEventMask()); // 4) Now simulate proxy closing the TCP connection: read() returns -1. final ConnectionClosedException ex = assertThrows( ConnectionClosedException.class, () -> handler.inputReady(session, null), "EOF during SOCKS handshake must fail the exchange"); // This is what the reactor would do: route the exception to the handler. handler.exception(session, ex); assertTrue(sessionRequest.isDone(), "Session request future must be completed"); final ExecutionException ee = assertThrows(ExecutionException.class, sessionRequest::get); assertSame(ex, ee.getCause(), "Cause must be the original EOF/close exception"); assertEquals(CloseMode.IMMEDIATE, session.getLastCloseMode(), "Session must be closed immediately"); } private static final class TestIOSession implements IOSession { private final Lock lock; private final Deque commands; private volatile boolean open; private volatile int eventMask; private volatile IOEventHandler handler; private volatile Timeout socketTimeout; private volatile long lastReadTime; private volatile long lastWriteTime; private volatile long lastEventTime; private volatile CloseMode lastCloseMode; TestIOSession() { this.lock = new ReentrantLock(); this.commands = new ArrayDeque<>(); this.open = true; this.eventMask = 0; this.socketTimeout = Timeout.DISABLED; this.lastReadTime = System.currentTimeMillis(); this.lastWriteTime = this.lastReadTime; this.lastEventTime = this.lastReadTime; } CloseMode getLastCloseMode() { return this.lastCloseMode; } @Override public ByteChannel channel() { return this; } @Override public void setEventMask(final int ops) { this.eventMask = ops; this.lastEventTime = System.currentTimeMillis(); } @Override public int getEventMask() { return this.eventMask; } @Override public void setEvent(final int op) { setEventMask(this.eventMask | op); } @Override public void clearEvent(final int op) { setEventMask(this.eventMask & ~op); } @Override public IOEventHandler getHandler() { return this.handler; } @Override public void upgrade(final IOEventHandler handler) { this.handler = handler; } @Override public Lock getLock() { return this.lock; } @Override public void enqueue(final Command command, final Command.Priority priority) { // not needed for this regression this.commands.add(command); } @Override public boolean hasCommands() { return !this.commands.isEmpty(); } @Override public Command poll() { return this.commands.poll(); } @Override public SocketAddress getRemoteAddress() { return null; } @Override public SocketAddress getLocalAddress() { return null; } @Override public Timeout getSocketTimeout() { return this.socketTimeout; } @Override public void setSocketTimeout(final Timeout timeout) { this.socketTimeout = timeout; this.lastEventTime = System.currentTimeMillis(); } @Override public long getLastReadTime() { return this.lastReadTime; } @Override public long getLastWriteTime() { return this.lastWriteTime; } @Override public long getLastEventTime() { return this.lastEventTime; } @Override public void updateReadTime() { this.lastReadTime = System.currentTimeMillis(); this.lastEventTime = this.lastReadTime; } @Override public void updateWriteTime() { this.lastWriteTime = System.currentTimeMillis(); this.lastEventTime = this.lastWriteTime; } @Override public Status getStatus() { return this.open ? Status.ACTIVE : Status.CLOSED; } @Override public String getId() { return "test"; } @Override public int read(final ByteBuffer dst) throws IOException { // Simulate EOF from proxy. return -1; } @Override public int write(final ByteBuffer src) throws IOException { final int n = src.remaining(); src.position(src.limit()); // drain updateWriteTime(); return n; } @Override public boolean isOpen() { return this.open; } @Override public void close() { this.open = false; } @Override public void close(final CloseMode closeMode) { this.lastCloseMode = closeMode; this.open = false; // ensure any pending commands are failed/cancelled on close paths if they exist CommandSupport.cancelCommands(this); } } } httpcore5/src/test/java/org/apache/hc/core5/reactor/ssl/0040755 0000000 0000000 00000000000 00000250600 022051 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/reactor/ssl/SSLIOSessionTest.java0100644 0000000 0000000 00000017050 00000250600 026011 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactor.ssl; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import java.nio.ByteBuffer; import java.security.Provider; import java.util.concurrent.locks.ReentrantLock; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContextSpi; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSession; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.reactor.IOEventHandler; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; class SSLIOSessionTest { // Define common variables here, so you can easily modify them in each test private NamedEndpoint targetEndpoint; private IOSession ioSession; private SSLMode sslMode; private SSLContext sslContext; private SSLBufferMode sslBufferMode; private SSLSessionInitializer initializer; private SSLSessionVerifier verifier; private Timeout handshakeTimeout; private Callback sessionStartCallback; private Callback sessionEndCallback; private FutureCallback resultCallback; private IOEventHandler ioEventHandler; private SSLEngine mockSSLEngine; private SSLSession sslSession; @BeforeEach void setUp() throws SSLException { final String protocol = "TestProtocol"; // Arrange targetEndpoint = mock(NamedEndpoint.class); ioSession = mock(IOSession.class); sslMode = SSLMode.CLIENT; // Use actual SSLMode //SSLContext sslContext = SSLContext.getDefault(); final SSLContextSpi sslContextSpi = mock(SSLContextSpi.class); final Provider provider = mock(Provider.class); sslContext = new TestSSLContext(sslContextSpi, provider, protocol); sslSession = mock(SSLSession.class); sslBufferMode = SSLBufferMode.STATIC; initializer = mock(SSLSessionInitializer.class); verifier = mock(SSLSessionVerifier.class); handshakeTimeout = mock(Timeout.class); sessionStartCallback = mock(Callback.class); sessionEndCallback = mock(Callback.class); resultCallback = mock(FutureCallback.class); ioEventHandler = mock(IOEventHandler.class); // Mock behavior of targetEndpoint Mockito.when(targetEndpoint.getHostName()).thenReturn("testHostName"); Mockito.when(targetEndpoint.getPort()).thenReturn(8080); Mockito.when(sslSession.getPacketBufferSize()).thenReturn(1024); Mockito.when(sslSession.getApplicationBufferSize()).thenReturn(1024); // Mock behavior of ioSession Mockito.when(ioSession.getEventMask()).thenReturn(1); Mockito.when(ioSession.getLock()).thenReturn(new ReentrantLock()); // Mock behavior of sslContext and SSLEngine mockSSLEngine = mock(SSLEngine.class); Mockito.when(sslContext.createSSLEngine(any(String.class), any(Integer.class))).thenReturn(mockSSLEngine); Mockito.when(mockSSLEngine.getSession()).thenReturn(sslSession); Mockito.when(mockSSLEngine.getHandshakeStatus()).thenReturn(SSLEngineResult.HandshakeStatus.NEED_WRAP); Mockito.when(ioSession.getHandler()).thenReturn(ioEventHandler); final SSLEngineResult mockResult = new SSLEngineResult(SSLEngineResult.Status.CLOSED, SSLEngineResult.HandshakeStatus.FINISHED, 0, 464); Mockito.when(mockSSLEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenReturn(mockResult); } @Test void testConstructorWhenSSLEngineOk() { final String protocol = "TestProtocol"; // Arrange Mockito.when(mockSSLEngine.getApplicationProtocol()).thenReturn(protocol); // Act final TestableSSLIOSession sslioSession = new TestableSSLIOSession(targetEndpoint, ioSession, sslMode, sslContext, sslBufferMode, initializer, verifier, handshakeTimeout, sessionStartCallback, sessionEndCallback, resultCallback); // Assert Assertions.assertDoesNotThrow(() -> sslioSession.beginHandshake(ioSession)); Assertions.assertEquals(protocol, sslioSession.getTlsDetails().getApplicationProtocol()); } @Test void testConstructorWhenSSLEngineThrowsException() { final String protocol = "http/1.1"; // Arrange Mockito.when(mockSSLEngine.getApplicationProtocol()).thenThrow(UnsupportedOperationException.class); // Act final TestableSSLIOSession sslioSession = new TestableSSLIOSession(targetEndpoint, ioSession, sslMode, sslContext, sslBufferMode, initializer, verifier, handshakeTimeout, sessionStartCallback, sessionEndCallback, resultCallback); // Assert Assertions.assertDoesNotThrow(() -> sslioSession.beginHandshake(ioSession)); Assertions.assertEquals(protocol, sslioSession.getTlsDetails().getApplicationProtocol()); } static class TestSSLContext extends SSLContext { /** * Creates an SSLContext object. * * @param contextSpi the delegate * @param provider the provider * @param protocol the protocol */ protected TestSSLContext(final SSLContextSpi contextSpi, final Provider provider, final String protocol) { super(contextSpi, provider, protocol); } } static class TestableSSLIOSession extends SSLIOSession { TestableSSLIOSession(final NamedEndpoint targetEndpoint, final IOSession session, final SSLMode sslMode, final SSLContext sslContext, final SSLBufferMode sslBufferMode, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier, final Timeout handshakeTimeout, final Callback sessionStartCallback, final Callback sessionEndCallback, final FutureCallback resultCallback) { super(targetEndpoint, session, sslMode, sslContext, sslBufferMode, initializer, verifier, handshakeTimeout, sessionStartCallback, sessionEndCallback, resultCallback); } } } httpcore5/src/test/java/org/apache/hc/core5/ssl/0040755 0000000 0000000 00000000000 00000250600 020412 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/ssl/DummyProvider.java0100644 0000000 0000000 00000004755 00000250600 024073 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.ssl; import java.security.Provider; import java.security.Security; import java.util.HashSet; import java.util.Set; import java.util.concurrent.locks.ReentrantLock; public class DummyProvider extends Provider { private final Provider realJSSEProvider = Security.getProvider(TestSSLContextBuilder.PROVIDER_SUN_JSSE); private final Provider realJCEEProvider = Security.getProvider(TestSSLContextBuilder.PROVIDER_SUN_JCE); final static String NAME = "FAKE"; private final Set requestedTypes = new HashSet<>(); private final ReentrantLock lock; public DummyProvider() { super(NAME, 1.1, "http core fake provider 1.1"); this.lock = new ReentrantLock(); } public boolean hasBeenRequested(final String what) { return requestedTypes.contains(what); } @Override public Service getService(final String type, final String algorithm) { requestedTypes.add(type); if ("KeyStore".equals(type)) { return realJCEEProvider.getService(type, algorithm); } return realJSSEProvider.getService(type, algorithm); } @Override public Set getServices() { lock.lock(); try { return realJSSEProvider.getServices(); } finally { lock.unlock(); } } } httpcore5/src/test/java/org/apache/hc/core5/ssl/SSLContextsTest.java0100644 0000000 0000000 00000006523 00000250600 024311 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.ssl; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import org.junit.jupiter.api.Test; class SSLContextsTest { @Test void createDefault() { final SSLContext sslContext = SSLContexts.createDefault(); assertAll( () -> assertNotNull(sslContext), () -> assertEquals(SSLContextBuilder.TLS, sslContext.getProtocol()), () -> assertNotNull(sslContext.getProvider()) ); } @Test void createSystemDefault() { final SSLContext sslContext = SSLContexts.createSystemDefault(); assertAll( () -> assertNotNull(sslContext), () -> assertEquals("Default", sslContext.getProtocol()), () -> assertNotNull(sslContext.getProvider()) ); } @Test void custom() throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException { final SSLContext sslContext = SSLContexts.custom() .setKeyStoreType(KeyStore.getDefaultType()) .setKeyManagerFactoryAlgorithm(KeyManagerFactory.getDefaultAlgorithm()) .setTrustManagerFactoryAlgorithm(TrustManagerFactory.getDefaultAlgorithm()) .setProvider("SunJSSE") .setProtocol("TLS") .setSecureRandom(null) .loadTrustMaterial((KeyStore) null, null) .loadKeyMaterial((KeyStore) null, null, null) .build(); assertAll( () -> assertNotNull(sslContext), () -> assertEquals(SSLContextBuilder.TLS, sslContext.getProtocol()), () -> assertEquals("SunJSSE", sslContext.getProvider().getName()) ); } }httpcore5/src/test/java/org/apache/hc/core5/ssl/TestSSLContextBuilder.java0100644 0000000 0000000 00000110556 00000250600 025437 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.ssl; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.URL; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Principal; import java.security.Security; import java.security.UnrecoverableKeyException; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManagerFactory; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link SSLContextBuilder}. */ class TestSSLContextBuilder { static final String PROVIDER_SUN_JSSE = "SunJSSE"; static final String PROVIDER_SUN_JCE = "SunJCE"; private static boolean isWindows() { return System.getProperty("os.name").contains("Windows"); } private static final Timeout TIMEOUT = Timeout.ofSeconds(5); private ExecutorService executorService; @AfterEach void cleanup() throws Exception { if (this.executorService != null) { this.executorService.shutdown(); this.executorService.awaitTermination(5, TimeUnit.SECONDS); } } private URL getResource(final String name) { return getClass().getResource(name); } @Test void testBuildAllDefaults() throws Exception { final SSLContext sslContext = SSLContextBuilder.create() .setKeyStoreType(KeyStore.getDefaultType()) .setKeyManagerFactoryAlgorithm(KeyManagerFactory.getDefaultAlgorithm()) .setTrustManagerFactoryAlgorithm(TrustManagerFactory.getDefaultAlgorithm()) .setProvider(PROVIDER_SUN_JSSE) .setProtocol("TLS") .setSecureRandom(null) .loadTrustMaterial((KeyStore) null, null) .loadKeyMaterial((KeyStore) null, null, null) .build(); Assertions.assertNotNull(sslContext); Assertions.assertEquals("TLS", sslContext.getProtocol()); Assertions.assertEquals(PROVIDER_SUN_JSSE, sslContext.getProvider().getName()); } @Test void testBuildAllNull() throws Exception { final SSLContext sslContext = SSLContextBuilder.create() .setKeyStoreType(null) .setKeyManagerFactoryAlgorithm(null) .setTrustManagerFactoryAlgorithm(null) .setProtocol(null) .setProvider((String) null) .setSecureRandom(null) .loadTrustMaterial((KeyStore) null, null) .loadKeyMaterial((KeyStore) null, null, null) .build(); Assertions.assertNotNull(sslContext); Assertions.assertEquals("TLS", sslContext.getProtocol()); Assertions.assertEquals(PROVIDER_SUN_JSSE, sslContext.getProvider().getName()); } @Test void testBuildAllNull_deprecated() throws Exception { final SSLContext sslContext = SSLContextBuilder.create() .setProtocol(null) .setSecureRandom(null) .loadTrustMaterial((KeyStore) null, null) .loadKeyMaterial((KeyStore) null, null, null) .build(); Assertions.assertNotNull(sslContext); Assertions.assertEquals("TLS", sslContext.getProtocol()); } @Test void testBuildDefault() { Assertions.assertDoesNotThrow(() -> new SSLContextBuilder().build()); } @Test void testBuildNoSuchKeyManagerFactoryAlgorithm() { final URL resource1 = getResource("/test-keypasswd.p12"); final String storePassword = "nopassword"; final String keyPassword = "password"; Assertions.assertThrows(NoSuchAlgorithmException.class, () -> SSLContextBuilder.create() .setKeyManagerFactoryAlgorithm(" BAD ") .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray()) .build()); } @Test void testBuildNoSuchKeyStoreType() { final URL resource1 = getResource("/test-keypasswd.p12"); final String storePassword = "nopassword"; final String keyPassword = "password"; Assertions.assertThrows(KeyStoreException.class, () -> SSLContextBuilder.create() .setKeyStoreType(" BAD ") .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray()) .build()); } @Test void testBuildNoSuchTrustManagerFactoryAlgorithm() { final URL resource1 = getResource("/test-keypasswd.p12"); final String storePassword = "nopassword"; Assertions.assertThrows(NoSuchAlgorithmException.class, () -> SSLContextBuilder.create() .setTrustManagerFactoryAlgorithm(" BAD ") .loadTrustMaterial(resource1, storePassword.toCharArray()) .build()); } @Test void testBuildWithProvider() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; final DummyProvider provider = new DummyProvider(); SSLContextBuilder.create() .setProvider(provider) .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray()) .build(); Assertions.assertTrue(provider.hasBeenRequested("SSLContext")); } @Test void testBuildWithProviderName() throws Exception { final DummyProvider provider = new DummyProvider(); Security.insertProviderAt(provider, 1); try { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; SSLContextBuilder.create() .setProvider(DummyProvider.NAME) .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray()) .build(); Assertions.assertTrue(provider.hasBeenRequested("SSLContext")); } finally { Security.removeProvider(DummyProvider.NAME); } } @Test void testBuildKSWithNoSuchProvider() { Assertions.assertThrows(NoSuchProviderException.class, () -> SSLContextBuilder.create() .setKeyStoreProvider("no-such-provider") .build()); } @Test void testBuildKSWithProvider() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; final DummyProvider provider = new DummyProvider(); SSLContextBuilder.create() .setKeyStoreProvider(provider) .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray()) .build(); Assertions.assertTrue(provider.hasBeenRequested("KeyManagerFactory")); } @Test void testBuildKSWithProviderName() throws Exception { final DummyProvider provider = new DummyProvider(); Security.insertProviderAt(provider, 1); try { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; SSLContextBuilder.create() .setKeyStoreProvider(DummyProvider.NAME) .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray()) .build(); Assertions.assertTrue(provider.hasBeenRequested("KeyManagerFactory")); } finally { Security.removeProvider(DummyProvider.NAME); } } @Test void testBuildTSWithNoSuchProvider() { Assertions.assertThrows(NoSuchProviderException.class, () -> SSLContextBuilder.create() .setTrustStoreProvider("no-such-provider") .build()); } @Test void testBuildTSWithProvider() throws Exception { final DummyProvider provider = new DummyProvider(); SSLContextBuilder.create() .setTrustStoreProvider(provider) .loadTrustMaterial((KeyStore) null, null) .build(); Assertions.assertTrue(provider.hasBeenRequested("TrustManagerFactory")); } @Test void testBuildTSWithProviderName() throws Exception { final DummyProvider provider = new DummyProvider(); Security.insertProviderAt(provider, 1); try { SSLContextBuilder.create() .setTrustStoreProvider(DummyProvider.NAME) .loadTrustMaterial((KeyStore) null, null) .build(); Assertions.assertTrue(provider.hasBeenRequested("TrustManagerFactory")); } finally { Security.removeProvider(DummyProvider.NAME); } } @Test void testKeyWithAlternatePasswordInvalid() { final URL resource1 = getResource("/test-keypasswd.p12"); final String storePassword = "nopassword"; final String keyPassword = "!password"; Assertions.assertThrows(UnrecoverableKeyException.class, () -> SSLContextBuilder.create() .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray()) .loadTrustMaterial(resource1, storePassword.toCharArray()) .build()); } @Test void testSSLHandshakeServerTrusted() throws Exception { final URL resource1 = getResource("/test.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; final SSLContext serverSslContext = SSLContextBuilder.create() .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray()) .build(); Assertions.assertNotNull(serverSslContext); final SSLContext clientSslContext = SSLContextBuilder.create() .loadTrustMaterial(resource1, storePassword.toCharArray()) .build(); Assertions.assertNotNull(clientSslContext); final ServerSocket serverSocket = serverSslContext.getServerSocketFactory().createServerSocket(); serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); this.executorService = Executors.newSingleThreadExecutor(); final Future future = this.executorService.submit(() -> { try (Socket socket = serverSocket.accept()) { final OutputStream outputStream = socket.getOutputStream(); outputStream.write(new byte[]{'H', 'i'}); outputStream.flush(); } return Boolean.TRUE; }); final int localPort = serverSocket.getLocalPort(); try (final Socket clientSocket = clientSslContext.getSocketFactory().createSocket()) { clientSocket.connect(new InetSocketAddress("localhost", localPort), TIMEOUT.toMillisecondsIntBound()); clientSocket.setSoTimeout(TIMEOUT.toMillisecondsIntBound()); final InputStream inputStream = clientSocket.getInputStream(); Assertions.assertEquals('H', inputStream.read()); Assertions.assertEquals('i', inputStream.read()); Assertions.assertEquals(-1, inputStream.read()); } final Boolean result = future.get(5, TimeUnit.SECONDS); Assertions.assertNotNull(result); } @Test void testSSLHandshakeServerNotTrusted() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; final SSLContext serverSslContext = SSLContextBuilder.create() .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray()) .build(); Assertions.assertNotNull(serverSslContext); final URL resource2 = getResource("/test.p12"); final SSLContext clientSslContext = SSLContextBuilder.create() .loadTrustMaterial(resource2, storePassword.toCharArray()) .build(); Assertions.assertNotNull(clientSslContext); final ServerSocket serverSocket = serverSslContext.getServerSocketFactory().createServerSocket(); serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); this.executorService = Executors.newSingleThreadExecutor(); this.executorService.submit(() -> { try (SSLSocket socket = (SSLSocket) serverSocket.accept()) { socket.getSession(); } return Boolean.FALSE; }); final int localPort = serverSocket.getLocalPort(); try (final SSLSocket clientSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket()) { clientSocket.connect(new InetSocketAddress("localhost", localPort), TIMEOUT.toMillisecondsIntBound()); clientSocket.setSoTimeout(TIMEOUT.toMillisecondsIntBound()); Assertions.assertThrows(IOException.class, clientSocket::startHandshake); } } @Test void testSSLHandshakeServerCustomTrustStrategy() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; final SSLContext serverSslContext = SSLContextBuilder.create() .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray()) .build(); Assertions.assertNotNull(serverSslContext); final AtomicReference certChainRef = new AtomicReference<>(); final TrustStrategy trustStrategy = (chain, authType) -> { certChainRef.set(chain); return true; }; final SSLContext clientSslContext = SSLContextBuilder.create() .loadTrustMaterial(trustStrategy) .build(); Assertions.assertNotNull(clientSslContext); final ServerSocket serverSocket = serverSslContext.getServerSocketFactory().createServerSocket(); serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); this.executorService = Executors.newSingleThreadExecutor(); final Future future = this.executorService.submit(() -> { try (Socket socket = serverSocket.accept()) { final OutputStream outputStream = socket.getOutputStream(); outputStream.write(new byte[]{'H', 'i'}); outputStream.flush(); } return Boolean.TRUE; }); final int localPort = serverSocket.getLocalPort(); try (final SSLSocket clientSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket()) { clientSocket.connect(new InetSocketAddress("localhost", localPort), TIMEOUT.toMillisecondsIntBound()); clientSocket.setSoTimeout(TIMEOUT.toMillisecondsIntBound()); final InputStream inputStream = clientSocket.getInputStream(); Assertions.assertEquals('H', inputStream.read()); Assertions.assertEquals('i', inputStream.read()); Assertions.assertEquals(-1, inputStream.read()); } final Boolean result = future.get(5, TimeUnit.SECONDS); Assertions.assertNotNull(result); final X509Certificate[] certs = certChainRef.get(); Assertions.assertNotNull(certs); Assertions.assertEquals(2, certs.length); final X509Certificate cert1 = certs[0]; final Principal subjectDN1 = cert1.getSubjectDN(); Assertions.assertNotNull(subjectDN1); Assertions.assertEquals("CN=Test Server, OU=HttpComponents Project, O=Apache Software Foundation", subjectDN1.getName()); final X509Certificate cert2 = certs[1]; final Principal subjectDN2 = cert2.getSubjectDN(); Assertions.assertNotNull(subjectDN2); Assertions.assertEquals("EMAILADDRESS=dev@hc.apache.org, " + "CN=Test CA, OU=HttpComponents Project, O=Apache Software Foundation", subjectDN2.getName()); final Principal issuerDN = cert2.getIssuerDN(); Assertions.assertNotNull(issuerDN); Assertions.assertEquals("EMAILADDRESS=dev@hc.apache.org, " + "CN=Test CA, OU=HttpComponents Project, O=Apache Software Foundation", issuerDN.getName()); } @Test void testSSLHandshakeClientUnauthenticated() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; final SSLContext serverSslContext = SSLContextBuilder.create() .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray()) .build(); Assertions.assertNotNull(serverSslContext); final URL resource2 = getResource("/test-client.p12"); final SSLContext clientSslContext = SSLContextBuilder.create() .loadTrustMaterial(resource2, storePassword.toCharArray()) .build(); Assertions.assertNotNull(clientSslContext); final SSLServerSocket serverSocket = (SSLServerSocket) serverSslContext.getServerSocketFactory().createServerSocket(); serverSocket.setWantClientAuth(true); serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); this.executorService = Executors.newSingleThreadExecutor(); final Future future = this.executorService.submit(() -> { Principal clientPrincipal = null; try (SSLSocket socket = (SSLSocket) serverSocket.accept()) { final SSLSession session = socket.getSession(); try { clientPrincipal = session.getPeerPrincipal(); } catch (final SSLPeerUnverifiedException ignore) { } final OutputStream outputStream = socket.getOutputStream(); outputStream.write(new byte [] {'H', 'i'}); outputStream.flush(); } return clientPrincipal; }); final int localPort = serverSocket.getLocalPort(); try (final SSLSocket clientSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket()) { clientSocket.connect(new InetSocketAddress("localhost", localPort), TIMEOUT.toMillisecondsIntBound()); clientSocket.setSoTimeout(TIMEOUT.toMillisecondsIntBound()); clientSocket.startHandshake(); final InputStream inputStream = clientSocket.getInputStream(); Assertions.assertEquals('H', inputStream.read()); Assertions.assertEquals('i', inputStream.read()); Assertions.assertEquals(-1, inputStream.read()); } final Principal clientPrincipal = future.get(5, TimeUnit.SECONDS); Assertions.assertNull(clientPrincipal); } @Test void testSSLHandshakeClientUnauthenticatedError() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; final SSLContext serverSslContext = SSLContextBuilder.create() .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray()) .build(); Assertions.assertNotNull(serverSslContext); final URL resource2 = getResource("/test-client.p12"); final SSLContext clientSslContext = SSLContextBuilder.create() .loadTrustMaterial(resource2, storePassword.toCharArray()) .build(); Assertions.assertNotNull(clientSslContext); final SSLServerSocket serverSocket = (SSLServerSocket) serverSslContext.getServerSocketFactory().createServerSocket(); serverSocket.setNeedClientAuth(true); serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); this.executorService = Executors.newSingleThreadExecutor(); this.executorService.submit(() -> { try (SSLSocket socket = (SSLSocket) serverSocket.accept()) { socket.getSession(); } return Boolean.FALSE; }); final int localPort = serverSocket.getLocalPort(); try (final SSLSocket clientSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket()) { clientSocket.connect(new InetSocketAddress("localhost", localPort), TIMEOUT.toMillisecondsIntBound()); clientSocket.setSoTimeout(TIMEOUT.toMillisecondsIntBound()); Assertions.assertThrows(IOException.class, () -> { clientSocket.startHandshake(); final InputStream inputStream = clientSocket.getInputStream(); inputStream.read(); }); } } @Test void testSSLHandshakeClientAuthenticated() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; final SSLContext serverSslContext = SSLContextBuilder.create() .loadTrustMaterial(resource1, storePassword.toCharArray()) .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray()) .build(); Assertions.assertNotNull(serverSslContext); final URL resource2 = getResource("/test-client.p12"); final SSLContext clientSslContext = SSLContextBuilder.create() .loadTrustMaterial(resource2, storePassword.toCharArray()) .loadKeyMaterial(resource2, storePassword.toCharArray(), storePassword.toCharArray()) .build(); Assertions.assertNotNull(clientSslContext); final SSLServerSocket serverSocket = (SSLServerSocket) serverSslContext.getServerSocketFactory().createServerSocket(); serverSocket.setNeedClientAuth(true); serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); this.executorService = Executors.newSingleThreadExecutor(); final Future future = this.executorService.submit(() -> { try (SSLSocket socket = (SSLSocket) serverSocket.accept()) { final SSLSession session = socket.getSession(); final Principal clientPrincipal = session.getPeerPrincipal(); final OutputStream outputStream = socket.getOutputStream(); outputStream.write(new byte[]{'H', 'i'}); outputStream.flush(); return clientPrincipal; } }); final int localPort = serverSocket.getLocalPort(); try (final SSLSocket clientSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket()) { clientSocket.connect(new InetSocketAddress("localhost", localPort), TIMEOUT.toMillisecondsIntBound()); clientSocket.setSoTimeout(TIMEOUT.toMillisecondsIntBound()); clientSocket.startHandshake(); final InputStream inputStream = clientSocket.getInputStream(); Assertions.assertEquals('H', inputStream.read()); Assertions.assertEquals('i', inputStream.read()); Assertions.assertEquals(-1, inputStream.read()); } final Principal clientPrincipal = future.get(5, TimeUnit.SECONDS); Assertions.assertNotNull(clientPrincipal); } @Test void testSSLHandshakeClientAuthenticatedPrivateKeyStrategy() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; final SSLContext serverSslContext = SSLContextBuilder.create() .loadTrustMaterial(resource1, storePassword.toCharArray()) .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray()) .build(); Assertions.assertNotNull(serverSslContext); final PrivateKeyStrategy privateKeyStrategy = (aliases, sslParameters) -> aliases.containsKey("client2") ? "client2" : null; final URL resource2 = getResource("/test-client.p12"); final SSLContext clientSslContext = SSLContextBuilder.create() .loadTrustMaterial(resource2, storePassword.toCharArray()) .loadKeyMaterial(resource2, storePassword.toCharArray(), storePassword.toCharArray(), privateKeyStrategy) .build(); Assertions.assertNotNull(clientSslContext); final SSLServerSocket serverSocket = (SSLServerSocket) serverSslContext.getServerSocketFactory().createServerSocket(); serverSocket.setNeedClientAuth(true); serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); this.executorService = Executors.newSingleThreadExecutor(); final Future future = this.executorService.submit(() -> { try (SSLSocket socket = (SSLSocket) serverSocket.accept()) { final SSLSession session = socket.getSession(); final Principal clientPrincipal = session.getPeerPrincipal(); final OutputStream outputStream = socket.getOutputStream(); outputStream.write(new byte[]{'H', 'i'}); outputStream.flush(); return clientPrincipal; } }); final int localPort = serverSocket.getLocalPort(); try (final SSLSocket clientSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket()) { clientSocket.connect(new InetSocketAddress("localhost", localPort), TIMEOUT.toMillisecondsIntBound()); clientSocket.setSoTimeout(TIMEOUT.toMillisecondsIntBound()); clientSocket.startHandshake(); final InputStream inputStream = clientSocket.getInputStream(); Assertions.assertEquals('H', inputStream.read()); Assertions.assertEquals('i', inputStream.read()); Assertions.assertEquals(-1, inputStream.read()); } final Principal clientPrincipal = future.get(5, TimeUnit.SECONDS); Assertions.assertNotNull(clientPrincipal); Assertions.assertEquals("CN=Test Client 2,OU=HttpComponents Project,O=Apache Software Foundation", clientPrincipal.getName()); } @Test void testSSLHandshakeProtocolMismatch1() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; final SSLContext serverSslContext = SSLContextBuilder.create() .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray()) .build(); Assertions.assertNotNull(serverSslContext); final URL resource2 = getResource("/test-client.p12"); final SSLContext clientSslContext = SSLContextBuilder.create() .loadTrustMaterial(resource2, storePassword.toCharArray()) .build(); Assertions.assertNotNull(clientSslContext); final SSLServerSocket serverSocket = (SSLServerSocket) serverSslContext.getServerSocketFactory().createServerSocket(); final Set supportedServerProtocols = new LinkedHashSet<>(Arrays.asList(serverSocket.getSupportedProtocols())); Assertions.assertTrue(supportedServerProtocols.contains("TLSv1")); serverSocket.setEnabledProtocols(new String[] {"TLSv1"}); serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); this.executorService = Executors.newSingleThreadExecutor(); this.executorService.submit(() -> { try (SSLSocket socket = (SSLSocket) serverSocket.accept()) { socket.getSession(); } return Boolean.FALSE; }); final int localPort = serverSocket.getLocalPort(); try (final SSLSocket clientSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket()) { final Set supportedClientProtocols = new LinkedHashSet<>(Arrays.asList(clientSocket.getSupportedProtocols())); Assertions.assertTrue(supportedClientProtocols.contains("SSLv3")); clientSocket.setEnabledProtocols(new String[] {"SSLv3"} ); clientSocket.connect(new InetSocketAddress("localhost", localPort), TIMEOUT.toMillisecondsIntBound()); clientSocket.setSoTimeout(TIMEOUT.toMillisecondsIntBound()); if (isWindows()) { Assertions.assertThrows(IOException.class, clientSocket::startHandshake); } else { Assertions.assertThrows(SSLException.class, clientSocket::startHandshake); } } } @Test void testSSLHandshakeProtocolMismatch2() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; final SSLContext serverSslContext = SSLContextBuilder.create() .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray()) .build(); Assertions.assertNotNull(serverSslContext); final URL resource2 = getResource("/test-client.p12"); final SSLContext clientSslContext = SSLContextBuilder.create() .loadTrustMaterial(resource2, storePassword.toCharArray()) .build(); Assertions.assertNotNull(clientSslContext); final SSLServerSocket serverSocket = (SSLServerSocket) serverSslContext.getServerSocketFactory().createServerSocket(); final Set supportedServerProtocols = new LinkedHashSet<>(Arrays.asList(serverSocket.getSupportedProtocols())); Assertions.assertTrue(supportedServerProtocols.contains("SSLv3")); serverSocket.setEnabledProtocols(new String[] {"SSLv3"}); serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); this.executorService = Executors.newSingleThreadExecutor(); this.executorService.submit(() -> { try (SSLSocket socket = (SSLSocket) serverSocket.accept()) { socket.getSession(); } return Boolean.FALSE; }); final int localPort = serverSocket.getLocalPort(); try (final SSLSocket clientSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket()) { final Set supportedClientProtocols = new LinkedHashSet<>( Arrays.asList(clientSocket.getSupportedProtocols())); Assertions.assertTrue(supportedClientProtocols.contains("TLSv1")); clientSocket.setEnabledProtocols(new String[]{"TLSv1"}); clientSocket.connect(new InetSocketAddress("localhost", localPort), TIMEOUT.toMillisecondsIntBound()); clientSocket.setSoTimeout(TIMEOUT.toMillisecondsIntBound()); if (isWindows()) { Assertions.assertThrows(IOException.class, clientSocket::startHandshake); } else { Assertions.assertThrows(SSLException.class, clientSocket::startHandshake); } } } @Test void testJSSEEndpointIdentification() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; final SSLContext serverSslContext = SSLContextBuilder.create() .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray()) .build(); Assertions.assertNotNull(serverSslContext); final URL resource2 = getResource("/test-client.p12"); final SSLContext clientSslContext = SSLContextBuilder.create() .loadTrustMaterial(resource2, storePassword.toCharArray()) .build(); Assertions.assertNotNull(clientSslContext); final SSLServerSocket serverSocket = (SSLServerSocket) serverSslContext.getServerSocketFactory().createServerSocket(); serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); this.executorService = Executors.newSingleThreadExecutor(); this.executorService.submit(() -> { for (;;) { try (SSLSocket socket = (SSLSocket) serverSocket.accept()) { socket.getSession(); socket.shutdownOutput(); } catch (final IOException ex) { return Boolean.FALSE; } } }); final int localPort1 = serverSocket.getLocalPort(); try (final Socket clientSocket = new Socket()) { clientSocket.connect(new InetSocketAddress("localhost", localPort1)); try (SSLSocket sslSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket(clientSocket, "localhost", -1, true)) { final SSLParameters sslParameters = sslSocket.getSSLParameters(); sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); sslSocket.setSSLParameters(sslParameters); sslSocket.startHandshake(); } } final int localPort2 = serverSocket.getLocalPort(); try (final Socket clientSocket = new Socket()) { clientSocket.connect(new InetSocketAddress("localhost", localPort2)); try (SSLSocket sslSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket(clientSocket, "otherhost", -1, true)) { final SSLParameters sslParameters = sslSocket.getSSLParameters(); sslParameters.setEndpointIdentificationAlgorithm(null); sslSocket.setSSLParameters(sslParameters); sslSocket.startHandshake(); } } final int localPort3 = serverSocket.getLocalPort(); Assertions.assertThrows(SSLException.class, () -> { try (final Socket clientSocket = new Socket()) { clientSocket.connect(new InetSocketAddress("localhost", localPort3)); try (SSLSocket sslSocket = (SSLSocket) clientSslContext.getSocketFactory().createSocket(clientSocket, "otherhost", -1, true)) { final SSLParameters sslParameters = sslSocket.getSSLParameters(); sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); sslSocket.setSSLParameters(sslParameters); sslSocket.startHandshake(); } } }); } } httpcore5/src/test/java/org/apache/hc/core5/util/0040755 0000000 0000000 00000000000 00000250600 020566 5ustar000000000 0000000 httpcore5/src/test/java/org/apache/hc/core5/util/TestArgs.java0100644 0000000 0000000 00000021457 00000250600 023173 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import java.text.ParseException; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link Args}. */ class TestArgs { @Test void testArgCheckPass() { Args.check(true, "All is well"); } @Test void testArgCheckFail() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.check(false, "Oopsie")); } @Test void testArgNotNullPass() { final String stuff = "stuff"; Assertions.assertSame(stuff, Args.notNull(stuff, "Stuff")); } @Test void testArgNotNullFail() { Assertions.assertThrows(NullPointerException.class, () -> Args.notNull(null, "Stuff")); } @Test void testArgNotEmptyPass() { final String stuff = "stuff"; Assertions.assertSame(stuff, Args.notEmpty(stuff, "Stuff")); } @Test void testArgNotEmptyFail1() { Assertions.assertThrows(NullPointerException.class, () -> Args.notEmpty((String) null, "Stuff")); } @Test void testArgNotEmptyFail2() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.notEmpty("", "Stuff")); } @Test void testArgNotBlankFail1() { Assertions.assertThrows(NullPointerException.class, () -> Args.notBlank((String) null, "Stuff")); } @Test void testArgNotBlankFail2() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.notBlank("", "Stuff")); } @Test void testArgNotBlankFail3() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.notBlank(" \t \n\r", "Stuff")); } @Test void testArgCollectionNotEmptyPass() { final List list = Collections.singletonList("stuff"); Assertions.assertSame(list, Args.notEmpty(list, "List")); } @Test void testArgCollectionNotEmptyFail1() { Assertions.assertThrows(NullPointerException.class, () -> Args.notEmpty((List) null, "List")); } @Test void testArgCollectionNotEmptyFail2() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.notEmpty(Collections.emptyList(), "List")); } @Test void testPositiveIntPass() { Assertions.assertEquals(1, Args.positive(1, "Number")); } @Test void testPositiveIntFail1() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.positive(-1, "Number")); } @Test void testPositiveIntFail2() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.positive(0, "Number")); } @Test void testPositiveLongPass() { Assertions.assertEquals(1L, Args.positive(1L, "Number")); } @Test void testPositiveTimeValuePass() throws ParseException { final Timeout timeout = Timeout.parse("1200 MILLISECONDS"); Assertions.assertEquals(timeout, Args.positive(timeout, "No Error")); } @Test void testPositiveLongFail1() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.positive(-1L, "Number")); } @Test void testPositiveLongFail2() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.positive(0L, "Number")); } @Test void testNotNegativeIntPass1() { Assertions.assertEquals(1, Args.notNegative(1, "Number")); } @Test void testNotNegativeIntPass2() { Assertions.assertEquals(0, Args.notNegative(0, "Number")); } @Test void testNotNegativeIntFail1() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.notNegative(-1, "Number")); } @Test void testNotNegativeLongPass1() { Assertions.assertEquals(1L, Args.notNegative(1L, "Number")); } @Test void testNotNegativeLongPass2() { Assertions.assertEquals(0L, Args.notNegative(0L, "Number")); } @Test void testNotNegativeLongFail1() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.notNegative(-1L, "Number")); } @Test void testIntSmallestRangeOK() { Args.checkRange(0, 0, 0, "Number"); } @Test void testIntSmallestRangeFailLow() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.checkRange(-1, 0, 0, "Number")); } @Test void testIntRangeFailLow() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.checkRange(-101, -100, 100, "Number")); } @Test void testIntRangeFailHigh() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.checkRange(101, -100, 100, "Number")); } @Test void testIntSmallestRangeFailHigh() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.checkRange(1, 0, 0, "Number")); } @Test void testIntFullRangeOK() { Args.checkRange(0, Integer.MIN_VALUE, Integer.MAX_VALUE, "Number"); } @Test void testLongSmallestRangeOK() { Args.checkRange(0L, 0L, 0L, "Number"); } @Test void testLongSmallestRangeFailLow() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.checkRange(-1L, 0L, 0L, "Number")); } @Test void testLongRangeFailLow() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.checkRange(-101L, -100L, 100L, "Number")); } @Test void testLongRangeFailHigh() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.checkRange(101L, -100L, 100L, "Number")); } @Test void testLongSmallestRangeFailHigh() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.checkRange(1L, 0L, 0L, "Number")); } @Test void testLongFullRangeOK() { Args.checkRange(0L, Long.MIN_VALUE, Long.MAX_VALUE, "Number"); } @Test void testIsEmpty() { final String[] NON_EMPTY_ARRAY = new String[] { "ABG", "NML", }; final List NON_EMPTY_LIST = Arrays.asList(NON_EMPTY_ARRAY); final Set NON_EMPTY_SET = new HashSet<>(NON_EMPTY_LIST); final Map NON_EMPTY_MAP = new HashMap<>(); NON_EMPTY_MAP.put("ABG", "MNL"); Assertions.assertTrue(Args.isEmpty(null)); Assertions.assertTrue(Args.isEmpty("")); Assertions.assertTrue(Args.isEmpty(new int[] {})); Assertions.assertTrue(Args.isEmpty(Collections.emptyList())); Assertions.assertTrue(Args.isEmpty(Collections.emptySet())); Assertions.assertTrue(Args.isEmpty(Collections.emptyMap())); Assertions.assertFalse(Args.isEmpty(" ")); Assertions.assertFalse(Args.isEmpty("ab")); Assertions.assertFalse(Args.isEmpty(NON_EMPTY_ARRAY)); Assertions.assertFalse(Args.isEmpty(NON_EMPTY_LIST)); Assertions.assertFalse(Args.isEmpty(NON_EMPTY_SET)); Assertions.assertFalse(Args.isEmpty(NON_EMPTY_MAP)); } @Test void testContainsNoBlanks() { final String stuff = "abg"; Assertions.assertSame(stuff, Args.containsNoBlanks(stuff, "abg")); } @Test void check() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.check(false, "Error,", "ABG")); } } httpcore5/src/test/java/org/apache/hc/core5/util/TestAsserts.java0100644 0000000 0000000 00000005172 00000250600 023717 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link Asserts}. */ class TestAsserts { @Test void testExpressionCheckPass() { Asserts.check(true, "All is well"); } @Test void testExpressionCheckFail() { Assertions.assertThrows(IllegalStateException.class, () -> Asserts.check(false, "Oopsie")); } @Test void testExpressionNotNullFail() { Assertions.assertThrows(IllegalStateException.class, () -> Asserts.notNull(null, "Stuff")); } @Test void testExpressionNotEmptyFail1() { Assertions.assertThrows(IllegalStateException.class, () -> Asserts.notEmpty(null, "Stuff")); } @Test void testExpressionNotEmptyFail2() { Assertions.assertThrows(IllegalStateException.class, () -> Asserts.notEmpty("", "Stuff")); } @Test void testExpressionNotEmptyBlank1() { Assertions.assertThrows(IllegalStateException.class, () -> Asserts.notBlank(null, "Stuff")); } @Test void testExpressionNotEmptyBlank2() { Assertions.assertThrows(IllegalStateException.class, () -> Asserts.notBlank("", "Stuff")); } @Test void testExpressionNotBlankFail3() { Assertions.assertThrows(IllegalStateException.class, () -> Asserts.notBlank(" \t \n\r", "Stuff")); } } httpcore5/src/test/java/org/apache/hc/core5/util/TestByteArrayBuffer.java0100644 0000000 0000000 00000034236 00000250600 025332 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link ByteArrayBuffer}. * */ class TestByteArrayBuffer { @Test void testConstructor() { final ByteArrayBuffer buffer = new ByteArrayBuffer(16); Assertions.assertEquals(16, buffer.capacity()); Assertions.assertEquals(0, buffer.length()); Assertions.assertNotNull(buffer.array()); Assertions.assertEquals(16, buffer.array().length); Assertions.assertThrows(IllegalArgumentException.class, () -> new ByteArrayBuffer(-1)); } @Test void testSimpleAppend() { final ByteArrayBuffer buffer = new ByteArrayBuffer(16); Assertions.assertEquals(16, buffer.capacity()); Assertions.assertEquals(0, buffer.length()); final byte[] b1 = buffer.toByteArray(); Assertions.assertNotNull(b1); Assertions.assertEquals(0, b1.length); Assertions.assertTrue(buffer.isEmpty()); Assertions.assertFalse(buffer.isFull()); final byte[] tmp = new byte[] { 1, 2, 3, 4}; buffer.append(tmp, 0, tmp.length); Assertions.assertEquals(16, buffer.capacity()); Assertions.assertEquals(4, buffer.length()); Assertions.assertFalse(buffer.isEmpty()); Assertions.assertFalse(buffer.isFull()); final byte[] b2 = buffer.toByteArray(); Assertions.assertNotNull(b2); Assertions.assertEquals(4, b2.length); for (int i = 0; i < tmp.length; i++) { Assertions.assertEquals(tmp[i], b2[i]); Assertions.assertEquals(tmp[i], buffer.byteAt(i)); } buffer.clear(); Assertions.assertEquals(16, buffer.capacity()); Assertions.assertEquals(0, buffer.length()); Assertions.assertTrue(buffer.isEmpty()); Assertions.assertFalse(buffer.isFull()); } @Test void testExpandAppend() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); Assertions.assertEquals(4, buffer.capacity()); final byte[] tmp = new byte[] { 1, 2, 3, 4}; buffer.append(tmp, 0, 2); buffer.append(tmp, 0, 4); buffer.append(tmp, 0, 0); Assertions.assertEquals(8, buffer.capacity()); Assertions.assertEquals(6, buffer.length()); buffer.append(tmp, 0, 4); Assertions.assertEquals(16, buffer.capacity()); Assertions.assertEquals(10, buffer.length()); } @Test void testAppendHeapByteBuffer() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); Assertions.assertEquals(4, buffer.capacity()); final ByteBuffer tmp = ByteBuffer.wrap(new byte[] { 1, 2, 3, 4, 5, 6}); buffer.append(tmp); Assertions.assertFalse(tmp.hasRemaining(), "The input buffer should be drained"); Assertions.assertEquals(8, buffer.capacity()); Assertions.assertEquals(6, buffer.length()); tmp.clear(); buffer.append(tmp); Assertions.assertFalse(tmp.hasRemaining(), "The input buffer should be drained"); Assertions.assertEquals(16, buffer.capacity()); Assertions.assertEquals(12, buffer.length()); Assertions.assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6}, buffer.toByteArray()); } @Test void testAppendHeapByteBufferWithOffset() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); Assertions.assertEquals(4, buffer.capacity()); final ByteBuffer tmp = ByteBuffer.wrap(new byte[] { 7, 7, 1, 2, 3, 4, 5, 6, 7, 7}, 2, 6).slice(); Assertions.assertTrue(tmp.arrayOffset() > 0, "Validate this is testing a buffer with an array offset"); buffer.append(tmp); Assertions.assertFalse(tmp.hasRemaining(), "The input buffer should be drained"); Assertions.assertEquals(8, buffer.capacity()); Assertions.assertEquals(6, buffer.length()); tmp.clear(); Assertions.assertEquals(6, tmp.remaining()); buffer.append(tmp); Assertions.assertFalse(tmp.hasRemaining(), "The input buffer should be drained"); Assertions.assertEquals(16, buffer.capacity()); Assertions.assertEquals(12, buffer.length()); Assertions.assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6}, buffer.toByteArray()); } @Test void testAppendDirectByteBuffer() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); Assertions.assertEquals(4, buffer.capacity()); final ByteBuffer tmp = ByteBuffer.allocateDirect(6); tmp.put(new byte[] { 1, 2, 3, 4, 5, 6}).flip(); buffer.append(tmp); Assertions.assertFalse(tmp.hasRemaining(), "The input buffer should be drained"); Assertions.assertEquals(8, buffer.capacity()); Assertions.assertEquals(6, buffer.length()); tmp.clear(); buffer.append(tmp); Assertions.assertFalse(tmp.hasRemaining(), "The input buffer should be drained"); Assertions.assertEquals(16, buffer.capacity()); Assertions.assertEquals(12, buffer.length()); Assertions.assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6}, buffer.toByteArray()); } @Test void testInvalidAppend() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); buffer.append((byte[])null, 0, 0); final byte[] tmp = new byte[] { 1, 2, 3, 4}; Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, -1, 0)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, 0, -1)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, 0, 8)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, 10, Integer.MAX_VALUE)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, 2, 4)); } @Test void testAppendOneByte() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); Assertions.assertEquals(4, buffer.capacity()); final byte[] tmp = new byte[] { 1, 127, -1, -128, 1, -2}; for (final byte element : tmp) { buffer.append(element); } Assertions.assertEquals(8, buffer.capacity()); Assertions.assertEquals(6, buffer.length()); for (int i = 0; i < tmp.length; i++) { Assertions.assertEquals(tmp[i], buffer.byteAt(i)); } } @Test void testSetLength() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); buffer.setLength(2); Assertions.assertEquals(2, buffer.length()); } @Test void testSetInvalidLength() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.setLength(-2)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.setLength(200)); } @Test void testEnsureCapacity() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); buffer.ensureCapacity(2); Assertions.assertEquals(4, buffer.capacity()); buffer.ensureCapacity(8); Assertions.assertEquals(8, buffer.capacity()); } @Test void testIndexOf() { final byte COLON = (byte) ':'; final byte COMMA = (byte) ','; final byte[] bytes = "name1: value1; name2: value2".getBytes(StandardCharsets.US_ASCII); final int index1 = 5; final int index2 = 20; final ByteArrayBuffer buffer = new ByteArrayBuffer(16); buffer.append(bytes, 0, bytes.length); Assertions.assertEquals(index1, buffer.indexOf(COLON)); Assertions.assertEquals(-1, buffer.indexOf(COMMA)); Assertions.assertEquals(index1, buffer.indexOf(COLON, -1, 11)); Assertions.assertEquals(index1, buffer.indexOf(COLON, 0, 1000)); Assertions.assertEquals(-1, buffer.indexOf(COLON, 2, 1)); Assertions.assertEquals(index2, buffer.indexOf(COLON, index1 + 1, buffer.length())); } @Test void testAppendCharArrayAsAscii() { final String s1 = "stuff"; final String s2 = " and more stuff"; final char[] b1 = s1.toCharArray(); final char[] b2 = s2.toCharArray(); final ByteArrayBuffer buffer = new ByteArrayBuffer(8); buffer.append(b1, 0, b1.length); buffer.append(b2, 0, b2.length); Assertions.assertEquals(s1 + s2, new String(buffer.toByteArray(), StandardCharsets.US_ASCII)); } @Test void testAppendNullCharArray() { final ByteArrayBuffer buffer = new ByteArrayBuffer(8); buffer.append((char[])null, 0, 0); Assertions.assertEquals(0, buffer.length()); } @Test void testAppendEmptyCharArray() { final ByteArrayBuffer buffer = new ByteArrayBuffer(8); buffer.append(new char[] {}, 0, 0); Assertions.assertEquals(0, buffer.length()); } @Test void testAppendNullCharArrayBuffer() { final ByteArrayBuffer buffer = new ByteArrayBuffer(8); buffer.append((CharArrayBuffer)null, 0, 0); Assertions.assertEquals(0, buffer.length()); } @Test void testAppendNullByteBuffer() { final ByteArrayBuffer buffer = new ByteArrayBuffer(8); final ByteBuffer nullBuffer = null; buffer.append(nullBuffer); Assertions.assertEquals(0, buffer.length()); } @Test void testInvalidAppendCharArrayAsAscii() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); buffer.append((char[])null, 0, 0); final char[] tmp = new char[] { '1', '2', '3', '4'}; Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, -1, 0)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, 0, -1)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, 0, 8)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, 10, Integer.MAX_VALUE)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, 2, 4)); } @Test void testSerialization() throws Exception { final ByteArrayBuffer orig = new ByteArrayBuffer(32); orig.append(1); orig.append(2); orig.append(3); final ByteArrayOutputStream outbuffer = new ByteArrayOutputStream(); try (final ObjectOutputStream outStream = new ObjectOutputStream(outbuffer)) { outStream.writeObject(orig); } final byte[] raw = outbuffer.toByteArray(); final ByteArrayInputStream inBuffer = new ByteArrayInputStream(raw); final ObjectInputStream inStream = new ObjectInputStream(inBuffer); final ByteArrayBuffer clone = (ByteArrayBuffer) inStream.readObject(); Assertions.assertEquals(orig.capacity(), clone.capacity()); Assertions.assertEquals(orig.length(), clone.length()); final byte[] data = clone.toByteArray(); Assertions.assertNotNull(data); Assertions.assertEquals(3, data.length); Assertions.assertEquals(1, data[0]); Assertions.assertEquals(2, data[1]); Assertions.assertEquals(3, data[2]); } @Test void testControlCharFiltering() { final char[] chars = new char[256]; for (char i = 0; i < 256; i++) { chars[i] = i; } final byte[] bytes = asByteArray(chars); Assertions.assertEquals( "?????????\t??????????????????????" + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz" + "{|}~???????????????????????" + "??????????\u00A0¡¢£¤¥¦§¨©ª«¬\u00AD®¯" + "°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ" + "ÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîï" + "ðñòóôõö÷øùúûüýþÿ", new String(bytes, StandardCharsets.ISO_8859_1)); } @Test void testUnicodeFiltering() { // Various languages Assertions.assertEquals("?????", new String(asByteArray("буквы".toCharArray()), StandardCharsets.ISO_8859_1)); Assertions.assertEquals("????", new String(asByteArray("四字熟語".toCharArray()), StandardCharsets.ISO_8859_1)); // Unicode snowman Assertions.assertEquals("?", new String(asByteArray("☃".toCharArray()), StandardCharsets.ISO_8859_1)); // Emoji (surrogate pair) Assertions.assertEquals("??", new String(asByteArray("\uD83D\uDE00".toCharArray()), StandardCharsets.ISO_8859_1)); } private static byte[] asByteArray(final char[] chars) { final ByteArrayBuffer byteArrayBuffer = new ByteArrayBuffer(chars.length); byteArrayBuffer.append(chars, 0, chars.length); return byteArrayBuffer.toByteArray(); } } httpcore5/src/test/java/org/apache/hc/core5/util/TestCharArrayBuffer.java0100644 0000000 0000000 00000031356 00000250600 025304 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link CharArrayBuffer}. * */ class TestCharArrayBuffer { @Test void testConstructor() { final CharArrayBuffer buffer = new CharArrayBuffer(16); Assertions.assertEquals(16, buffer.capacity()); Assertions.assertEquals(0, buffer.length()); Assertions.assertNotNull(buffer.array()); Assertions.assertEquals(16, buffer.array().length); Assertions.assertThrows(IllegalArgumentException.class, () -> new CharArrayBuffer(-1)); } @Test void testSimpleAppend() { final CharArrayBuffer buffer = new CharArrayBuffer(16); Assertions.assertEquals(16, buffer.capacity()); Assertions.assertEquals(0, buffer.length()); final char[] b1 = buffer.toCharArray(); Assertions.assertNotNull(b1); Assertions.assertEquals(0, b1.length); Assertions.assertTrue(buffer.isEmpty()); Assertions.assertFalse(buffer.isFull()); final char[] tmp = new char[] { '1', '2', '3', '4'}; buffer.append(tmp, 0, tmp.length); Assertions.assertEquals(16, buffer.capacity()); Assertions.assertEquals(4, buffer.length()); Assertions.assertFalse(buffer.isEmpty()); Assertions.assertFalse(buffer.isFull()); final char[] b2 = buffer.toCharArray(); Assertions.assertNotNull(b2); Assertions.assertEquals(4, b2.length); for (int i = 0; i < tmp.length; i++) { Assertions.assertEquals(tmp[i], b2[i]); Assertions.assertEquals(tmp[i], buffer.charAt(i)); } Assertions.assertEquals("1234", buffer.toString()); buffer.clear(); Assertions.assertEquals(16, buffer.capacity()); Assertions.assertEquals(0, buffer.length()); Assertions.assertTrue(buffer.isEmpty()); Assertions.assertFalse(buffer.isFull()); } @Test void testExpandAppend() { final CharArrayBuffer buffer = new CharArrayBuffer(4); Assertions.assertEquals(4, buffer.capacity()); final char[] tmp = new char[] { '1', '2', '3', '4'}; buffer.append(tmp, 0, 2); buffer.append(tmp, 0, 4); buffer.append(tmp, 0, 0); Assertions.assertEquals(8, buffer.capacity()); Assertions.assertEquals(6, buffer.length()); buffer.append(tmp, 0, 4); Assertions.assertEquals(16, buffer.capacity()); Assertions.assertEquals(10, buffer.length()); Assertions.assertEquals("1212341234", buffer.toString()); } @Test void testAppendString() { final CharArrayBuffer buffer = new CharArrayBuffer(8); buffer.append("stuff"); buffer.append(" and more stuff"); Assertions.assertEquals("stuff and more stuff", buffer.toString()); } @Test void testAppendNullString() { final CharArrayBuffer buffer = new CharArrayBuffer(8); buffer.append((String)null); Assertions.assertEquals("null", buffer.toString()); } @Test void testAppendCharArrayBuffer() { final CharArrayBuffer buffer1 = new CharArrayBuffer(8); buffer1.append(" and more stuff"); final CharArrayBuffer buffer2 = new CharArrayBuffer(8); buffer2.append("stuff"); buffer2.append(buffer1); Assertions.assertEquals("stuff and more stuff", buffer2.toString()); } @Test void testAppendNullCharArrayBuffer() { final CharArrayBuffer buffer = new CharArrayBuffer(8); buffer.append((CharArrayBuffer)null); buffer.append((CharArrayBuffer)null, 0, 0); Assertions.assertEquals("", buffer.toString()); } @Test void testAppendSingleChar() { final CharArrayBuffer buffer = new CharArrayBuffer(4); buffer.append('1'); buffer.append('2'); buffer.append('3'); buffer.append('4'); buffer.append('5'); buffer.append('6'); Assertions.assertEquals("123456", buffer.toString()); } @Test void testInvalidCharArrayAppend() { final CharArrayBuffer buffer = new CharArrayBuffer(4); buffer.append((char[])null, 0, 0); final char[] tmp = new char[] { '1', '2', '3', '4'}; Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, -1, 0)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, 0, -1)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, 0, 8)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, 10, Integer.MAX_VALUE)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, 2, 4)); } @Test void testSetLength() { final CharArrayBuffer buffer = new CharArrayBuffer(4); buffer.setLength(2); Assertions.assertEquals(2, buffer.length()); } @Test void testSetInvalidLength() { final CharArrayBuffer buffer = new CharArrayBuffer(4); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.setLength(-2)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.setLength(200)); } @Test void testEnsureCapacity() { final CharArrayBuffer buffer = new CharArrayBuffer(4); buffer.ensureCapacity(2); Assertions.assertEquals(4, buffer.capacity()); buffer.ensureCapacity(8); Assertions.assertEquals(8, buffer.capacity()); } @Test void testIndexOf() { final CharArrayBuffer buffer = new CharArrayBuffer(16); buffer.append("name: value"); Assertions.assertEquals(4, buffer.indexOf(':')); Assertions.assertEquals(-1, buffer.indexOf(',')); Assertions.assertEquals(4, buffer.indexOf(':', -1, 11)); Assertions.assertEquals(4, buffer.indexOf(':', 0, 1000)); Assertions.assertEquals(-1, buffer.indexOf(':', 2, 1)); } @Test void testSubstring() { final CharArrayBuffer buffer = new CharArrayBuffer(16); buffer.append(" name: value "); Assertions.assertEquals(5, buffer.indexOf(':')); Assertions.assertEquals(" name", buffer.substring(0, 5)); Assertions.assertEquals(" value ", buffer.substring(6, buffer.length())); Assertions.assertEquals("name", buffer.substringTrimmed(0, 5)); Assertions.assertEquals("value", buffer.substringTrimmed(6, buffer.length())); Assertions.assertEquals("", buffer.substringTrimmed(13, buffer.length())); } @Test void testSubstringIndexOfOutBound() { final CharArrayBuffer buffer = new CharArrayBuffer(16); buffer.append("stuff"); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.substring(-2, 10)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.substringTrimmed(-2, 10)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.substring(12, 10)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.substringTrimmed(12, 10)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.substring(2, 1)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.substringTrimmed(2, 1)); } @Test void testAppendAsciiByteArray() { final String s1 = "stuff"; final String s2 = " and more stuff"; final byte[] b1 = s1.getBytes(StandardCharsets.US_ASCII); final byte[] b2 = s2.getBytes(StandardCharsets.US_ASCII); final CharArrayBuffer buffer = new CharArrayBuffer(8); buffer.append(b1, 0, b1.length); buffer.append(b2, 0, b2.length); Assertions.assertEquals("stuff and more stuff", buffer.toString()); } @Test void testAppendISOByteArray() { final byte[] b = new byte[] {0x00, 0x20, 0x7F, -0x80, -0x01}; final CharArrayBuffer buffer = new CharArrayBuffer(8); buffer.append(b, 0, b.length); final char[] ch = buffer.toCharArray(); Assertions.assertNotNull(ch); Assertions.assertEquals(5, ch.length); Assertions.assertEquals(0x00, ch[0]); Assertions.assertEquals(0x20, ch[1]); Assertions.assertEquals(0x7F, ch[2]); Assertions.assertEquals(0x80, ch[3]); Assertions.assertEquals(0xFF, ch[4]); } @Test void testAppendNullByteArray() { final CharArrayBuffer buffer = new CharArrayBuffer(8); buffer.append((byte[])null, 0, 0); Assertions.assertEquals("", buffer.toString()); } @Test void testAppendNullByteArrayBuffer() { final CharArrayBuffer buffer = new CharArrayBuffer(8); buffer.append((ByteArrayBuffer)null, 0, 0); Assertions.assertEquals("", buffer.toString()); } @Test void testInvalidAppendAsciiByteArray() { final CharArrayBuffer buffer = new CharArrayBuffer(4); buffer.append((byte[])null, 0, 0); final byte[] tmp = new byte[] { '1', '2', '3', '4'}; Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, -1, 0)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, 0, -1)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, 0, 8)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, 10, Integer.MAX_VALUE)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.append(tmp, 2, 4)); } @Test void testSerialization() throws Exception { final CharArrayBuffer orig = new CharArrayBuffer(32); orig.append('a'); orig.append('b'); orig.append('c'); final ByteArrayOutputStream outbuffer = new ByteArrayOutputStream(); try (final ObjectOutputStream outStream = new ObjectOutputStream(outbuffer)) { outStream.writeObject(orig); } final byte[] raw = outbuffer.toByteArray(); final ByteArrayInputStream inBuffer = new ByteArrayInputStream(raw); final ObjectInputStream inStream = new ObjectInputStream(inBuffer); final CharArrayBuffer clone = (CharArrayBuffer) inStream.readObject(); Assertions.assertEquals(orig.capacity(), clone.capacity()); Assertions.assertEquals(orig.length(), clone.length()); final char[] data = clone.toCharArray(); Assertions.assertNotNull(data); Assertions.assertEquals(3, data.length); Assertions.assertEquals('a', data[0]); Assertions.assertEquals('b', data[1]); Assertions.assertEquals('c', data[2]); } @Test void testSubSequence() { final CharArrayBuffer buffer = new CharArrayBuffer(16); buffer.append(" name: value "); Assertions.assertEquals(5, buffer.indexOf(':')); Assertions.assertEquals(" name", buffer.subSequence(0, 5).toString()); Assertions.assertEquals(" value ", buffer.subSequence(6, buffer.length()).toString()); } @Test void testSubSequenceIndexOfOutBound() { final CharArrayBuffer buffer = new CharArrayBuffer(16); buffer.append("stuff"); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.subSequence(-2, 10)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.subSequence(12, 10)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.subSequence(2, 1)); } } httpcore5/src/test/java/org/apache/hc/core5/util/TestDeadline.java0100644 0000000 0000000 00000013356 00000250600 024003 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import java.text.ParseException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Tests {@link Deadline}. */ class TestDeadline { @Test void testFormat() throws ParseException { final Deadline deadline = Deadline.fromUnixMilliseconds(1000); final Deadline deadline2 = Deadline.parse(deadline.toString()); Assertions.assertEquals(1000, deadline2.getValue()); } @Test void testIsBefore() { final long nowPlusOneMin = System.currentTimeMillis() + 60000; final Deadline deadline = Deadline.fromUnixMilliseconds(nowPlusOneMin); Assertions.assertTrue(deadline.isBefore(nowPlusOneMin + 1)); } @Test void testIsExpired() { Assertions.assertTrue(Deadline.fromUnixMilliseconds(0).isExpired()); Assertions.assertTrue(Deadline.fromUnixMilliseconds(1).isExpired()); Assertions.assertFalse(Deadline.MAX_VALUE.isExpired()); Assertions.assertTrue(Deadline.MIN_VALUE.isExpired()); } @Test void testIsMax() { Assertions.assertFalse(Deadline.fromUnixMilliseconds(0).isMax()); Assertions.assertFalse(Deadline.fromUnixMilliseconds(1000).isMax()); Assertions.assertFalse(Deadline.MIN_VALUE.isMax()); Assertions.assertTrue(Deadline.MAX_VALUE.isMax()); } @Test void testIsMin() { Assertions.assertTrue(Deadline.fromUnixMilliseconds(0).isMin()); Assertions.assertFalse(Deadline.fromUnixMilliseconds(1000).isMin()); Assertions.assertFalse(Deadline.MAX_VALUE.isMin()); Assertions.assertTrue(Deadline.MIN_VALUE.isMin()); } @Test void testIsNotExpired() { Assertions.assertFalse(Deadline.fromUnixMilliseconds(0).isNotExpired()); Assertions.assertFalse(Deadline.fromUnixMilliseconds(1).isNotExpired()); Assertions.assertTrue(Deadline.MAX_VALUE.isNotExpired()); Assertions.assertFalse(Deadline.MIN_VALUE.isNotExpired()); } @Test void testMin() { Assertions.assertEquals(Deadline.MIN_VALUE, Deadline.MIN_VALUE.min(Deadline.MAX_VALUE)); Assertions.assertEquals(Deadline.MIN_VALUE, Deadline.MAX_VALUE.min(Deadline.MIN_VALUE)); // final Deadline deadline0 = Deadline.fromUnixMilliseconds(0); Assertions.assertEquals(Deadline.MIN_VALUE, deadline0.min(Deadline.MIN_VALUE)); Assertions.assertEquals(deadline0, deadline0.min(Deadline.MAX_VALUE)); // final Deadline deadline1 = Deadline.fromUnixMilliseconds(0); Assertions.assertEquals(Deadline.MIN_VALUE, deadline1.min(Deadline.MIN_VALUE)); Assertions.assertEquals(deadline0, deadline1.min(Deadline.MAX_VALUE)); } @Test void testParse() throws ParseException { final Deadline deadline = Deadline.parse("1969-12-31T17:00:01.000-0700"); Assertions.assertEquals(1000, deadline.getValue()); } @Test void testRemaining() { final int oneHourInMillis = 60_000 * 60; final long nowPlusOneHour = System.currentTimeMillis() + oneHourInMillis; final Deadline deadline = Deadline.fromUnixMilliseconds(nowPlusOneHour); Assertions.assertEquals(nowPlusOneHour, deadline.getValue()); Assertions.assertTrue(deadline.remaining() > 0); Assertions.assertTrue(deadline.remaining() <= oneHourInMillis); } @Test void testRemainingTimeValue() { final int oneHourInMillis = 60_000 * 60; final long nowPlusOneHour = System.currentTimeMillis() + oneHourInMillis; final Deadline deadline = Deadline.fromUnixMilliseconds(nowPlusOneHour); Assertions.assertEquals(nowPlusOneHour, deadline.getValue()); Assertions.assertTrue(deadline.remainingTimeValue().toNanoseconds() > 0); Assertions.assertTrue(deadline.remainingTimeValue().toMicroseconds() > 0); Assertions.assertTrue(deadline.remainingTimeValue().toMilliseconds() > 0); } @Test void testValue() { final long nowPlusOneMin = System.currentTimeMillis() + 60000; final Deadline deadline = Deadline.fromUnixMilliseconds(nowPlusOneMin); Assertions.assertEquals(nowPlusOneMin, deadline.getValue()); } @Test void testOverflowHandling() { final long currentTime = Long.MAX_VALUE - 5000; // Simulate close to overflow final TimeValue tenSeconds = TimeValue.ofMilliseconds(10000); // 10 seconds final Deadline deadline = Deadline.calculate(currentTime, tenSeconds); Assertions.assertEquals(Deadline.MAX_VALUE, deadline, "Overflow should result in the maximum deadline value."); } } httpcore5/src/test/java/org/apache/hc/core5/util/TestDeadlineTimeoutException.java0100644 0000000 0000000 00000004050 00000250600 027220 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestDeadlineTimeoutException { @Test void testMessage() { final Deadline deadline = Deadline.fromUnixMilliseconds(1000).freeze(); Assertions.assertTrue(deadline.isExpired(), deadline.toString()); final String format = deadline.formatTarget(); final TimeValue diff = TimeValue.ofMilliseconds(deadline.remaining()); Assertions.assertEquals("Deadline: " + format + ", " + diff + " overdue", DeadlineTimeoutException.from(deadline).getMessage()); } @Test void testInfiniteDeadlineMessage() { final Deadline deadline = Deadline.calculate(Timeout.ZERO_MILLISECONDS); Assertions.assertEquals("No deadline (infinite)", DeadlineTimeoutException.from(deadline).getMessage()); } } httpcore5/src/test/java/org/apache/hc/core5/util/TestLangUtils.java0100644 0000000 0000000 00000004432 00000250600 024173 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link LangUtils}. * */ class TestLangUtils { @Test void testBasicHash() { final Integer i = Integer.valueOf(1234); final int h1 = LangUtils.hashCode(LangUtils.HASH_SEED, i.hashCode()); final int h2 = LangUtils.hashCode(LangUtils.HASH_SEED, i); Assertions.assertEquals(h1, h2); } @Test void testNullObjectHash() { final int h1 = LangUtils.hashCode(LangUtils.HASH_SEED, null); final int h2 = LangUtils.hashCode(LangUtils.HASH_SEED, 0); Assertions.assertEquals(h1, h2); } @Test void testBooleanHash() { final int h1 = LangUtils.hashCode(LangUtils.HASH_SEED, true); final int h2 = LangUtils.hashCode(LangUtils.HASH_SEED, false); final int h3 = LangUtils.hashCode(LangUtils.HASH_SEED, true); final int h4 = LangUtils.hashCode(LangUtils.HASH_SEED, false); Assertions.assertNotEquals(h1, h2); Assertions.assertEquals(h1, h3); Assertions.assertEquals(h2, h4); } } httpcore5/src/test/java/org/apache/hc/core5/util/TestTextUtils.java0100644 0000000 0000000 00000004671 00000250600 024243 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Unit tests for {@link TextUtils}. * */ class TestTextUtils { @Test void testTextEmpty() { Assertions.assertTrue(TextUtils.isEmpty(null)); Assertions.assertTrue(TextUtils.isEmpty("")); Assertions.assertFalse(TextUtils.isEmpty("\t")); } @Test void testTextBlank() { Assertions.assertTrue(TextUtils.isBlank(null)); Assertions.assertTrue(TextUtils.isBlank("")); Assertions.assertTrue(TextUtils.isBlank(" ")); Assertions.assertTrue(TextUtils.isBlank("\t")); } @Test void testTextContainsBlanks() { Assertions.assertFalse(TextUtils.containsBlanks(null)); Assertions.assertFalse(TextUtils.containsBlanks("")); Assertions.assertTrue(TextUtils.containsBlanks(" ")); Assertions.assertTrue(TextUtils.containsBlanks("\t")); Assertions.assertTrue(TextUtils.containsBlanks(" a")); Assertions.assertFalse(TextUtils.containsBlanks("a")); } @Test void testToHexString() { Assertions.assertEquals("000c2001ff", TextUtils.toHexString(new byte[] { 0, 12, 32, 1 , -1})); Assertions.assertNull(TextUtils.toHexString(null)); } } httpcore5/src/test/java/org/apache/hc/core5/util/TestTimeValue.java0100644 0000000 0000000 00000034507 00000250600 024172 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import static org.hamcrest.MatcherAssert.assertThat; import java.text.ParseException; import java.time.Duration; import java.util.concurrent.TimeUnit; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestTimeValue { private void checkToDays(final long value, final TimeUnit timeUnit) { Assertions.assertEquals(timeUnit.toDays(value), TimeValue.of(value, timeUnit).toDays()); } private void checkToHours(final long value, final TimeUnit timeUnit) { Assertions.assertEquals(timeUnit.toHours(value), TimeValue.of(value, timeUnit).toHours()); } private void checkToMicroseconds(final long value, final TimeUnit timeUnit) { Assertions.assertEquals(timeUnit.toMicros(value), TimeValue.of(value, timeUnit).toMicroseconds()); } private void checkToMilliseconds(final long value, final TimeUnit timeUnit) { Assertions.assertEquals(timeUnit.toMillis(value), TimeValue.of(value, timeUnit).toMilliseconds()); } private void checkToMinutes(final long value, final TimeUnit timeUnit) { Assertions.assertEquals(timeUnit.toMinutes(value), TimeValue.of(value, timeUnit).toMinutes()); } private void checkToNanoseconds(final long value, final TimeUnit timeUnit) { Assertions.assertEquals(timeUnit.toNanos(value), TimeValue.of(value, timeUnit).toNanoseconds()); } private void checkToSeconds(final long value, final TimeUnit timeUnit) { Assertions.assertEquals(timeUnit.toSeconds(value), TimeValue.of(value, timeUnit).toSeconds()); } private void test(final long value) { for (final TimeUnit timeUnit : TimeUnit.values()) { checkToDays(value, timeUnit); checkToHours(value, timeUnit); checkToMinutes(value, timeUnit); checkToSeconds(value, timeUnit); checkToMilliseconds(value, timeUnit); checkToMicroseconds(value, timeUnit); checkToNanoseconds(value, timeUnit); } } @Test void test0() { test(0); } @Test void test1() { test(1); } @Test void testConvert() { Assertions.assertEquals(0, TimeValue.ofMilliseconds(0).convert(TimeUnit.DAYS)); Assertions.assertEquals(1000, TimeValue.ofSeconds(1).convert(TimeUnit.MILLISECONDS)); } @Test void testDivide() { // nominator is 0, result should be 0. Assertions.assertEquals(0, TimeValue.ofMilliseconds(0).divide(2).toDays()); Assertions.assertEquals(0, TimeValue.ofMilliseconds(0).divide(2).toHours()); Assertions.assertEquals(0, TimeValue.ofMilliseconds(0).divide(2).toMicroseconds()); Assertions.assertEquals(0, TimeValue.ofMilliseconds(0).divide(2).toMilliseconds()); Assertions.assertEquals(0, TimeValue.ofMilliseconds(0).divide(2).toMinutes()); Assertions.assertEquals(0, TimeValue.ofMilliseconds(0).divide(2).toNanoseconds()); Assertions.assertEquals(0, TimeValue.ofMilliseconds(0).divide(2).toSeconds()); Assertions.assertEquals(0, TimeValue.ofMilliseconds(0).divide(2).toMillisecondsIntBound()); Assertions.assertEquals(0, TimeValue.ofMilliseconds(0).divide(2).toSecondsIntBound()); // Assertions.assertEquals(50, TimeValue.ofMilliseconds(100).divide(2).toMilliseconds()); Assertions.assertEquals(0, TimeValue.ofMinutes(1).divide(2).toSeconds()); Assertions.assertEquals(30, TimeValue.ofMinutes(1).divide(2, TimeUnit.SECONDS).toSeconds()); Assertions.assertEquals(30000, TimeValue.ofMinutes(1).divide(2, TimeUnit.MILLISECONDS).toMilliseconds()); } @Test void testDivideBy0() { Assertions.assertThrows(ArithmeticException.class, () -> TimeValue.ofMilliseconds(0).divide(0)); } private void testFactory(final TimeUnit timeUnit) { Assertions.assertEquals(timeUnit, TimeValue.of(1, timeUnit).getTimeUnit()); // final Duration duration = Duration.of(1, TimeValue.toChronoUnit(timeUnit)); assertConvertion(duration); } @Test void testFactoryForDays() { testFactory(TimeUnit.DAYS); } @Test void testFactoryForDuration() { assertConvertion(Duration.ZERO); assertConvertion(Duration.ofDays(1)); assertConvertion(Duration.ofHours(1)); assertConvertion(Duration.ofMillis(1)); assertConvertion(Duration.ofNanos(1)); assertConvertion(Duration.ofSeconds(1)); assertConvertion(Duration.ofSeconds(1, 1)); } private void assertConvertion(final Duration duration) { Assertions.assertEquals(duration, TimeValue.of(duration).toDuration()); } @Test void testFactoryForHours() { testFactory(TimeUnit.HOURS); } @Test void testFactoryForMicroseconds() { testFactory(TimeUnit.MICROSECONDS); } @Test void testFactoryForMilliseconds() { testFactory(TimeUnit.MILLISECONDS); } @Test void testFactoryForMinutes() { testFactory(TimeUnit.MINUTES); } @Test void testFactoryForNanoseconds() { testFactory(TimeUnit.NANOSECONDS); } @Test void testFactoryForSeconds() { testFactory(TimeUnit.SECONDS); } @Test void testMin() { final TimeValue nanos1 = TimeValue.ofNanoseconds(1); final TimeValue micros1 = TimeValue.ofMicroseconds(1); final TimeValue millis1 = TimeValue.ofMilliseconds(1); final TimeValue seconds1 = TimeValue.ofSeconds(1); final TimeValue minutes1 = TimeValue.ofMinutes(1); final TimeValue hours1 = TimeValue.ofHours(1); final TimeValue days1 = TimeValue.ofDays(1); // Assertions.assertEquals(TimeValue.ZERO_MILLISECONDS, TimeValue.ZERO_MILLISECONDS.min(nanos1)); Assertions.assertEquals(TimeValue.ZERO_MILLISECONDS, TimeValue.ZERO_MILLISECONDS.min(micros1)); Assertions.assertEquals(TimeValue.ZERO_MILLISECONDS, TimeValue.ZERO_MILLISECONDS.min(millis1)); Assertions.assertEquals(TimeValue.ZERO_MILLISECONDS, TimeValue.ZERO_MILLISECONDS.min(seconds1)); Assertions.assertEquals(TimeValue.ZERO_MILLISECONDS, TimeValue.ZERO_MILLISECONDS.min(minutes1)); Assertions.assertEquals(TimeValue.ZERO_MILLISECONDS, TimeValue.ZERO_MILLISECONDS.min(hours1)); Assertions.assertEquals(TimeValue.ZERO_MILLISECONDS, TimeValue.ZERO_MILLISECONDS.min(days1)); // Assertions.assertEquals(nanos1, nanos1.min(nanos1)); Assertions.assertEquals(nanos1, nanos1.min(micros1)); Assertions.assertEquals(nanos1, nanos1.min(millis1)); Assertions.assertEquals(nanos1, nanos1.min(seconds1)); Assertions.assertEquals(nanos1, nanos1.min(minutes1)); Assertions.assertEquals(nanos1, nanos1.min(hours1)); Assertions.assertEquals(nanos1, nanos1.min(days1)); // Assertions.assertEquals(nanos1, micros1.min(nanos1)); Assertions.assertEquals(micros1, micros1.min(micros1)); Assertions.assertEquals(micros1, micros1.min(millis1)); Assertions.assertEquals(micros1, micros1.min(seconds1)); Assertions.assertEquals(micros1, micros1.min(minutes1)); Assertions.assertEquals(micros1, micros1.min(hours1)); Assertions.assertEquals(micros1, micros1.min(days1)); // Assertions.assertEquals(nanos1, millis1.min(nanos1)); Assertions.assertEquals(micros1, millis1.min(micros1)); Assertions.assertEquals(millis1, millis1.min(millis1)); Assertions.assertEquals(millis1, millis1.min(seconds1)); Assertions.assertEquals(millis1, millis1.min(minutes1)); Assertions.assertEquals(millis1, millis1.min(hours1)); Assertions.assertEquals(millis1, millis1.min(days1)); // Assertions.assertEquals(nanos1, seconds1.min(nanos1)); Assertions.assertEquals(micros1, seconds1.min(micros1)); Assertions.assertEquals(millis1, seconds1.min(millis1)); Assertions.assertEquals(seconds1, seconds1.min(seconds1)); Assertions.assertEquals(seconds1, seconds1.min(minutes1)); Assertions.assertEquals(seconds1, seconds1.min(hours1)); Assertions.assertEquals(seconds1, seconds1.min(days1)); // Assertions.assertEquals(nanos1, minutes1.min(nanos1)); Assertions.assertEquals(micros1, minutes1.min(micros1)); Assertions.assertEquals(millis1, minutes1.min(millis1)); Assertions.assertEquals(seconds1, minutes1.min(seconds1)); Assertions.assertEquals(minutes1, minutes1.min(minutes1)); Assertions.assertEquals(minutes1, minutes1.min(hours1)); Assertions.assertEquals(minutes1, minutes1.min(days1)); // Assertions.assertEquals(nanos1, hours1.min(nanos1)); Assertions.assertEquals(micros1, hours1.min(micros1)); Assertions.assertEquals(millis1, hours1.min(millis1)); Assertions.assertEquals(seconds1, hours1.min(seconds1)); Assertions.assertEquals(minutes1, hours1.min(minutes1)); Assertions.assertEquals(hours1, hours1.min(hours1)); Assertions.assertEquals(hours1, hours1.min(days1)); // Assertions.assertEquals(nanos1, days1.min(nanos1)); Assertions.assertEquals(micros1, days1.min(micros1)); Assertions.assertEquals(millis1, days1.min(millis1)); Assertions.assertEquals(seconds1, days1.min(seconds1)); Assertions.assertEquals(minutes1, days1.min(minutes1)); Assertions.assertEquals(hours1, days1.min(hours1)); Assertions.assertEquals(days1, days1.min(days1)); } @Test void testMaxInt() { test(Integer.MAX_VALUE); } @Test void testMaxLong() { test(Long.MAX_VALUE); } @Test void testNegative1() { test(-1); } @Test void testToString() { Assertions.assertEquals("9223372036854775807 SECONDS", TimeValue.ofSeconds(Long.MAX_VALUE).toString()); Assertions.assertEquals("0 MILLISECONDS", TimeValue.ZERO_MILLISECONDS.toString()); } @Test void testFromString() throws ParseException { final TimeValue maxSeconds = TimeValue.ofSeconds(Long.MAX_VALUE); Assertions.assertEquals(maxSeconds, TimeValue.parse("9223372036854775807 SECONDS")); Assertions.assertEquals(maxSeconds, TimeValue.parse("9223372036854775807 SECONDS")); Assertions.assertEquals(maxSeconds, TimeValue.parse(" 9223372036854775807 SECONDS ")); Assertions.assertEquals(maxSeconds, TimeValue.parse("9223372036854775807 Seconds")); Assertions.assertEquals(maxSeconds, TimeValue.parse("9223372036854775807 Seconds")); Assertions.assertEquals(maxSeconds, TimeValue.parse("9223372036854775807\tSeconds")); Assertions.assertEquals(TimeValue.ZERO_MILLISECONDS, TimeValue.parse("0 MILLISECONDS")); Assertions.assertEquals(TimeValue.ofMilliseconds(1), TimeValue.parse("1 MILLISECOND")); } @Test void testToDuration() throws ParseException { Assertions.assertEquals(Long.MAX_VALUE, TimeValue.parse("9223372036854775807 SECONDS").toDuration().getSeconds()); } @Test void testEqualsAndHashCode() { final TimeValue tv1 = TimeValue.ofMilliseconds(1000L); final TimeValue tv2 = TimeValue.ofMilliseconds(1001L); final TimeValue tv3 = TimeValue.ofMilliseconds(1000L); final TimeValue tv4 = TimeValue.ofSeconds(1L); final TimeValue tv5 = TimeValue.ofSeconds(1000L); assertThat(tv1.equals(tv1), CoreMatchers.equalTo(true)); assertThat(tv1.equals(null), CoreMatchers.equalTo(false)); assertThat(tv1.equals(tv2), CoreMatchers.equalTo(false)); assertThat(tv1.equals(tv3), CoreMatchers.equalTo(true)); assertThat(tv1.equals(tv4), CoreMatchers.equalTo(true)); assertThat(tv4.equals(tv1), CoreMatchers.equalTo(true)); assertThat(tv1.equals(tv5), CoreMatchers.equalTo(false)); assertThat(tv1.hashCode() == tv2.hashCode(), CoreMatchers.equalTo(false)); assertThat(tv1.hashCode() == tv3.hashCode(), CoreMatchers.equalTo(true)); assertThat(tv1.hashCode() == tv4.hashCode(), CoreMatchers.equalTo(true)); assertThat(tv4.hashCode() == tv1.hashCode(), CoreMatchers.equalTo(true)); assertThat(tv1.hashCode() == tv5.hashCode(), CoreMatchers.equalTo(false)); } @Test void testCompareTo() { final TimeValue tv1 = TimeValue.ofMilliseconds(1000L); final TimeValue tv2 = TimeValue.ofMilliseconds(1001L); final TimeValue tv3 = TimeValue.ofMilliseconds(1000L); final TimeValue tv4 = TimeValue.ofSeconds(1L); final TimeValue tv5 = TimeValue.ofSeconds(60L); final TimeValue tv6 = TimeValue.ofMinutes(1L); assertThat(tv1.compareTo(tv1) == 0, CoreMatchers.equalTo(true)); assertThat(tv1.compareTo(tv2) < 0, CoreMatchers.equalTo(true)); assertThat(tv1.compareTo(tv3) == 0, CoreMatchers.equalTo(true)); assertThat(tv1.compareTo(tv4) == 0, CoreMatchers.equalTo(true)); assertThat(tv1.compareTo(tv5) < 0, CoreMatchers.equalTo(true)); assertThat(tv6.compareTo(tv5) == 0, CoreMatchers.equalTo(true)); assertThat(tv6.compareTo(tv4) > 0, CoreMatchers.equalTo(true)); Assertions.assertThrows(NullPointerException.class, () -> tv1.compareTo(null)); } } httpcore5/src/test/java/org/apache/hc/core5/util/TestTimeout.java0100644 0000000 0000000 00000013434 00000250600 023721 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import java.text.ParseException; import java.time.Duration; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestTimeout { private void checkToDays(final long value, final TimeUnit timeUnit) { Assertions.assertEquals(timeUnit.toDays(value), Timeout.of(value, timeUnit).toDays()); } private void checkToHours(final long value, final TimeUnit timeUnit) { Assertions.assertEquals(timeUnit.toHours(value), Timeout.of(value, timeUnit).toHours()); } private void checkToMicroseconds(final long value, final TimeUnit timeUnit) { Assertions.assertEquals(timeUnit.toMicros(value), Timeout.of(value, timeUnit).toMicroseconds()); } private void checkToMilliseconds(final long value, final TimeUnit timeUnit) { Assertions.assertEquals(timeUnit.toMillis(value), Timeout.of(value, timeUnit).toMilliseconds()); } private void checkToMinutes(final long value, final TimeUnit timeUnit) { Assertions.assertEquals(timeUnit.toMinutes(value), Timeout.of(value, timeUnit).toMinutes()); } private void checkToNanoseconds(final long value, final TimeUnit timeUnit) { Assertions.assertEquals(timeUnit.toNanos(value), Timeout.of(value, timeUnit).toNanoseconds()); } private void checkToSeconds(final long value, final TimeUnit timeUnit) { Assertions.assertEquals(timeUnit.toSeconds(value), Timeout.of(value, timeUnit).toSeconds()); } private void test(final long value) { for (final TimeUnit timeUnit : TimeUnit.values()) { checkToDays(value, timeUnit); checkToHours(value, timeUnit); checkToMinutes(value, timeUnit); checkToSeconds(value, timeUnit); checkToMilliseconds(value, timeUnit); checkToMicroseconds(value, timeUnit); checkToNanoseconds(value, timeUnit); } } @Test void test0() { test(0); } @Test void test1() { test(1); } @Test void testDisabled() { Assertions.assertTrue(Timeout.DISABLED.isDisabled()); Assertions.assertFalse(Timeout.DISABLED.isEnabled()); } private void testFactory(final TimeUnit timeUnit) { Assertions.assertEquals(timeUnit, Timeout.of(1, timeUnit).getTimeUnit()); } @Test void testFactoryForDays() { testFactory(TimeUnit.DAYS); } @Test void testFactoryForDuration() { assertConvertion(Duration.ZERO); assertConvertion(Duration.ofDays(1)); assertConvertion(Duration.ofHours(1)); assertConvertion(Duration.ofMillis(1)); assertConvertion(Duration.ofNanos(1)); assertConvertion(Duration.ofSeconds(1)); assertConvertion(Duration.ofSeconds(1, 1)); } private void assertConvertion(final Duration duration) { Assertions.assertEquals(duration, Timeout.of(duration).toDuration()); } @Test void testFactoryForHours() { testFactory(TimeUnit.HOURS); } @Test void testFactoryForMicroseconds() { testFactory(TimeUnit.MICROSECONDS); } @Test void testFactoryForMillisseconds() { testFactory(TimeUnit.MILLISECONDS); } @Test void testFactoryForMinutes() { testFactory(TimeUnit.MINUTES); } @Test void testFactoryForNanoseconds() { testFactory(TimeUnit.NANOSECONDS); } @Test void testFactoryForSeconds() { testFactory(TimeUnit.SECONDS); } @Test void testMaxInt() { test(Integer.MAX_VALUE); } @Test void testMaxLong() { test(Long.MAX_VALUE); } @Test void testNegative1() { Assertions.assertThrows(IllegalArgumentException.class, () -> test(-1)); } @Test void testToString() { Assertions.assertEquals("9223372036854775807 SECONDS", Timeout.ofSeconds(Long.MAX_VALUE).toString()); Assertions.assertEquals("0 MILLISECONDS", Timeout.ZERO_MILLISECONDS.toString()); } @Test void testFromString() throws ParseException { Assertions.assertEquals(Timeout.ofSeconds(Long.MAX_VALUE), Timeout.parse("9223372036854775807 SECONDS")); Assertions.assertEquals(Timeout.ofSeconds(Long.MAX_VALUE), Timeout.parse("9223372036854775807 Seconds")); Assertions.assertEquals(Timeout.ofSeconds(Long.MAX_VALUE), Timeout.parse("9223372036854775807 Seconds")); Assertions.assertEquals(Timeout.ofSeconds(Long.MAX_VALUE), Timeout.parse("9223372036854775807\tSeconds")); Assertions.assertEquals(Timeout.ZERO_MILLISECONDS, Timeout.parse("0 MILLISECONDS")); } } httpcore5/src/test/java/org/apache/hc/core5/util/TestTimeoutValueException.java0100644 0000000 0000000 00000003023 00000250600 026566 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestTimeoutValueException { @Test void testMessage() { Assertions.assertEquals("Timeout deadline: 1000 MILLISECONDS, actual: 2000 MILLISECONDS", TimeoutValueException.fromMilliseconds(1000, 2000).getMessage()); } } httpcore5/src/test/java/org/apache/hc/core5/util/TestTokenizer.java0100644 0000000 0000000 00000016502 00000250600 024244 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.util; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class TestTokenizer { private Tokenizer parser; @BeforeEach void setUp() { parser = new Tokenizer(); } private static CharArrayBuffer createBuffer(final String value) { if (value == null) { return null; } final CharArrayBuffer buffer = new CharArrayBuffer(value.length()); buffer.append(value); return buffer; } @Test void testBasicTokenParsing() { final String s = " raw: \" some stuff \""; final CharArrayBuffer raw = createBuffer(s); final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); parser.skipWhiteSpace(raw, cursor); Assertions.assertFalse(cursor.atEnd()); Assertions.assertEquals(3, cursor.getPos()); final StringBuilder strbuf1 = new StringBuilder(); parser.copyContent(raw, cursor, Tokenizer.delimiters(':'), strbuf1); Assertions.assertFalse(cursor.atEnd()); Assertions.assertEquals(6, cursor.getPos()); Assertions.assertEquals("raw", strbuf1.toString()); Assertions.assertEquals(':', raw.charAt(cursor.getPos())); cursor.updatePos(cursor.getPos() + 1); parser.skipWhiteSpace(raw, cursor); Assertions.assertFalse(cursor.atEnd()); Assertions.assertEquals(8, cursor.getPos()); final StringBuilder strbuf2 = new StringBuilder(); parser.copyQuotedContent(raw, cursor, strbuf2); Assertions.assertTrue(cursor.atEnd()); Assertions.assertEquals(" some stuff ", strbuf2.toString()); parser.copyQuotedContent(raw, cursor, strbuf2); Assertions.assertTrue(cursor.atEnd()); parser.skipWhiteSpace(raw, cursor); Assertions.assertTrue(cursor.atEnd()); } @Test void testTokenParsingWithQuotedPairs() { final String s = "raw: \"\\\"some\\stuff\\\\\""; final CharArrayBuffer raw = createBuffer(s); final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); parser.skipWhiteSpace(raw, cursor); Assertions.assertFalse(cursor.atEnd()); Assertions.assertEquals(0, cursor.getPos()); final StringBuilder strbuf1 = new StringBuilder(); parser.copyContent(raw, cursor, Tokenizer.delimiters(':'), strbuf1); Assertions.assertFalse(cursor.atEnd()); Assertions.assertEquals("raw", strbuf1.toString()); Assertions.assertEquals(':', raw.charAt(cursor.getPos())); cursor.updatePos(cursor.getPos() + 1); parser.skipWhiteSpace(raw, cursor); Assertions.assertFalse(cursor.atEnd()); final StringBuilder strbuf2 = new StringBuilder(); parser.copyQuotedContent(raw, cursor, strbuf2); Assertions.assertTrue(cursor.atEnd()); Assertions.assertEquals("\"some\\stuff\\", strbuf2.toString()); } @Test void testTokenParsingIncompleteQuote() { final String s = "\"stuff and more stuff "; final CharArrayBuffer raw = createBuffer(s); final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); final StringBuilder strbuf1 = new StringBuilder(); parser.copyQuotedContent(raw, cursor, strbuf1); Assertions.assertEquals("stuff and more stuff ", strbuf1.toString()); } @Test void testTokenParsingTokensWithUnquotedBlanks() { final String s = " stuff and \tsome\tmore stuff ;"; final CharArrayBuffer raw = createBuffer(s); final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); final String result = parser.parseToken(raw, cursor, Tokenizer.delimiters(';')); Assertions.assertEquals("stuff and some more stuff", result); } @Test void testTokenParsingMixedValuesAndQuotedValues() { final String s = " stuff and \" some more \" \"stuff ;"; final CharArrayBuffer raw = createBuffer(s); final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); final String result = parser.parseValue(raw, cursor, Tokenizer.delimiters(';')); Assertions.assertEquals("stuff and some more stuff ;", result); } @Test void testTokenParsingMixedValuesAndQuotedValues2() { final String s = "stuff\"more\"stuff;"; final CharArrayBuffer raw = createBuffer(s); final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); final String result = parser.parseValue(raw, cursor, Tokenizer.delimiters(';')); Assertions.assertEquals("stuffmorestuff", result); } @Test void testTokenParsingEscapedQuotes() { final String s = "stuff\"\\\"more\\\"\"stuff;"; final CharArrayBuffer raw = createBuffer(s); final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); final String result = parser.parseValue(raw, cursor, Tokenizer.delimiters(';')); Assertions.assertEquals("stuff\"more\"stuff", result); } @Test void testTokenParsingEscapedDelimiter() { final String s = "stuff\"\\\"more\\\";\"stuff;"; final CharArrayBuffer raw = createBuffer(s); final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); final String result = parser.parseValue(raw, cursor, Tokenizer.delimiters(';')); Assertions.assertEquals("stuff\"more\";stuff", result); } @Test void testTokenParsingEscapedSlash() { final String s = "stuff\"\\\"more\\\";\\\\\"stuff;"; final CharArrayBuffer raw = createBuffer(s); final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); final String result = parser.parseValue(raw, cursor, Tokenizer.delimiters(';')); Assertions.assertEquals("stuff\"more\";\\stuff", result); } @Test void testTokenParsingSlashOutsideQuotes() { final String s = "stuff\\; more stuff;"; final CharArrayBuffer raw = createBuffer(s); final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); final String result = parser.parseValue(raw, cursor, Tokenizer.delimiters(';')); Assertions.assertEquals("stuff\\", result); } } httpcore5/src/test/resources/0040755 0000000 0000000 00000000000 00000250600 015263 5ustar000000000 0000000 httpcore5/src/test/resources/ca.p120100644 0000000 0000000 00000005147 00000250600 016176 0ustar000000000 0000000 0 c0  *H   0 0a *H RN0J0F *H  00) *H  0./(63tOޠnQP|E5Ge{EvRQU$?ܸ)]w- v&9cz-8A K'}JQs/-OtB]._YW(f1Ir8]"XJ>]\HMkVܑVǣu9!h,u-~#O~8gc/lNJ7,j ڷRaүpQ~n |}a**GV;c>94EO`ӕo(tF0]Arv"ɽ޲3)!_I|2ΝsA-Y=dhjX\H`\XmXi]-aac?z|uRD5`m"TiC~c§J+UpL sM؂LPZ.J#.!nn2UW:p`)_:8;4ξQk&jcp< 8n~cFMv_@?|]8|8HH @W%7Ff/3 %_^09rz xr򭈻;G2UNWEH-]m%*D'hc_ 5wIm`e6[ҹOcaa~j;5nCq(̵`ZR.9Fs9~ '΢en& f(\F],ylT(-hF[of(%=I 4@ݪ7.oW!ror8X$>_$/cvWCO&}pdܲh㐸@X]@169*ݚ; 7Myv3y^νj8F{$^}e#YE]̟C㟔Ch.Tv Оv}: Iʍt ˊmEiYF ZcExA]$Ȱ;4fs9%Fi(ˏjXg3dk f&zݓ:.3qZuƩsM$E ѪFBI)Jc4Z>Fnd#IV  * 'E2P180 *H  1ca0! *H  1Time 15708093446860 *H 00 *H 0) *H  01㸪e](XPH>&&Uz<+ Yi DS| ,Qu&Twv&Bs`8r`ǧf \>|pWhXeJI 2f$ cta'9Ϊ9Bnh'6K ̸>qXF cuüYRH``^.-Jk{En:h Ckcfe ch ZǧS~a0JҜulA[78?|Qȍl0 m!n<~"4HXb S+9U-6ƥnr|Ӭ#5/$y %b(6iȑO!vGt34m٭5U y wX1]4+ctS28=bP}=Ĕ/8i7[{sCԢ\')`j^W<}MVTX8f09pC`"7)-MF\_¸}!KmrD =9?3Up( Qqw^9-wb6g Auc[flm 3}h ^8I1SoA*?Ys0,CNGҰC#7JIdy,v߆@YMժ_ T0Їu3 ׯT4e|zC h0ϲ\C7[ 猔@хfb-FlVܻMqBw..\LM[iZI-Ɲڑ7t+"u٠PK `ۉ1 Ů0!0 +X'_e`ڙh6&GjI<<3b~p[httpcore5/src/test/resources/test-client.p120100644 0000000 0000000 00000021215 00000250600 020040 0ustar000000000 0000000 0"0"B *H "3"/0"+0  *H   0 0P *H  00) *H  0/}Հz]O{FP3Wg. AdJHGM[ζ-He/ѵ͜tb e h8+E2ް=^EDyRL^9w}&oN'+ᾠ+gCBś#>@b &DsUax~m0&R.2xÇi08h@[n0̀b N=\#D1J yV kMLR#rwm$BΦUݦ`]"Ē*\y6w(Q":ظhs Ԫ!=Dc1B0 *H  1client10! *H  1Time 15708095392920P *H  00) *H  0N7o35W4LP8w9c(t&_&3l/E'h'şQ~3ҬTDkf"I{g4&Ehdĸ pz >#py~.Yz5rH,%J}SP)^༶Q5MvpVpbʮ|t@ieC]>:8Tvr{69ܸ9c5!ܝz |!Hr5S2A"G΄-nq^XEKnC! YWJCӖf2KA!(@T'](> Ը v/c{ a pLuD10KBeFbaڏ 5L m1.xL>(%;iBc=Z-IJ_c*B XA2N9ͩԃ}–`[Ba!h7lZPe3%\,<સ`/G 'BWMSٱCF|98@~sYW?1˴3r@_E{U'7eQg+aClK8v XIpp[R-2J9,T{H+A M&F tR頨&$*;2K؏< qRmIDWg,"ku4ĀyFHh-f?`3@#W`F1IrE޽{xb9v8Pa~G{VivhxM.˜iy^W6wȽatf c[WS@<:GdMA/VO\A5SI5S [Z הH&.s:?}+{8 /Mr–]&Nl@ݱ]LNio2;jxǂOјg;e" uRh7Ht{ᗵMMSXR:GVmMV܏ͻfɍ,e*+1`JTI]ߗ__yw (5h/f2B 47"hi)r.~S?v[-Wӊpt4kfW r*P(j޺P*AY޳dFy}!֏ҙuxjܰa[O^fcnpwG0Uì1B0 *H  1client20! *H  1Time 15708095451480d *H U0Q0J *H 0) *H  0xykʡR^(4XoEPVtn]2%IAK` \ZeV˞WAY =}(.&^)؍`>7*ŎM sH=>%='Vz<1b<|P p6w$ 0a̮p.4x&H&Y/1͎ۏe_5c0eSs'۷"__L1LbtaS'KpVB:PZ{^ /KsleY0#q09 !F.F5t+o09.4`I$Ђ qۭ ĄGtnؠ ʅS߹:*RLͺ EXp8_W8q_b-a&ru):Jn)E{9s4{+)-5rLL<.`O䰘&<ӳܸv,Ҝ(PN}&=b5w~NBCwrI,Lۦf`Rۙ=|̊Sá {d@HӝןM'w7%~K t\/N.:$z]/3&mTunnŁ#f84 O]'J^L;ֲx!M!;Gå#?!Ǹ֯'ΆUBpxm"MfhZI12S MDrt?]ujP(E䛇&$\/+M4S4Pط^VsJO:KH<4OYskêdӑ3OPJg8 IA+0uix=^*e!PCmDФ9&ʰITc|/3+~gD3~}6ƀ }d-7K15_Ri:1z*oFx>\?ڱxEء{|BD(WPLS`g}QNH52$AOs")[|_ngq3%لWD JцbiTɇDezOӋWa"Ƀ 6~H,f](nvG¸+|hW"qR({|us’Z4U $0; :}m0pJx (ߠɽn*{btrο";MA┟kyf*aRT XOUS~[Mwsr xGp}N((1}a\8ټcBӁ9v&*X.14қ`8L˲Hlǚ,y$UR?ԋڧ[t3NaX2l(Ø%—m|fs/$>m\$;Lb쫯&b5l?{1h29Aq~W6CG:]D`N";Z_(4[Tې_"LP/K,g&,x@OP{ <{KPT~+1L ʑ!&U!U &ߗg]@-b<4/3d`W:3B|`1ձ}>0ꗧ~&ƌz v5nzo[s];tV應Ѻ1hor2AR48Xb҈lW1!{膩$O Y|g9 +U6Gc\} sN~v/$Le^ߪN- CŔD1i\vPg z4b \]NS=MwIKZKv $M1bPZdztlEگϲEF'5U}Ѻi'oM47Az.' zo%9^I+nJz}%h7MߵHvˋgTě#+|Uѥo֞ [ͭeY 2B%slYSߝИ"0b'04̂u9`39F' ,/. c.0PuhOJRԧۯ+=s ",nbYdcnt{%?d)Z(ް +›mbMXyQ|bLmTTKgpiĖH _?PG6R'iGBzoCvU oK\]%.)u :x?J-~ǡYKJRIZT=w]tΤs;~BspmH)RRe'Q0~dr&Cjz^gڟ?d,_'yh~ne^5!o9yIO@:vmuRYZ);&&\H^σMɛc}G EݖPOiimԟ;%kлf J#P٦OOğ\B61&zZс*m|wtiԂ0BXTŲV歱3TF}vx9̷BlFL,ZG7"[!ªn\lc,kgْdMPmv Z(>hO)"I܈vQsͫ1^c8"GwǮJpRxo֟۞Z- oE?KvTu]@;gJ\ꑙ.#ԻnY'P_9AڲA !9$cqτbK@3Uk]@c+缶 W1CbXTu~ fPR afT~R:bi} RVgtAط98Jo:QvwUV45{ h/V *ٍjO2?>U9t/-&P0\3`Ǎc Y09Hwj`f񀫪b[L_ DKȸA4}ay)?1ϴ?L:sK.4՟Z2P#A WoEPJ9b]mal˝H3 .]ktcKc׬Iy풌l0к68^HWH|`v3 vA$;DJ4@Q4~mxcM;|A Ӟdzd|y&Yܮ\+HFO᭧p| c$kpo2g  M$ӶRϓOWu`^zIA[-sw ЃnA>Qծa朦::S U+.{NePa@/P" /H<[:ފJ^^>'q3Ny ?Ir\+eE^59p)[=kqYglMP9wP-ǁui虱p$D>O$j۩ M jZݯ/,2VlGߺ7.S@.*pi?ȸbv۲uo Ls~Z|7Y#ڹ&z-S?K1ITUEìIZ+S.|fYkLO;_vTv'͟(,%kP])fSh=T%@cZ6ʳhRr\B= xX)Q`o-C,qCL`8I"yDyU\J_tD{Q]54w6rNn_MYnߙ-01l0>0!0 +4}s?2!3M:VH5ֵhttpcore5/src/test/resources/test-keypasswd.p120100644 0000000 0000000 00000005077 00000250600 020604 0ustar000000000 0000000 0 ;0  *H   0 0 *H rn0j0f *H  00) *H  0 2eVV>k=,eg13GY}Y#=5ZfiLP(xFNdk |&d(~uda[%郬j 9]JDp%M1b}z6>cI 3TB͏rbF NCP^%@40{& dž0GREXm1yxJv= '$pT73a"&4(Ț+1%M?"P@jaz8l~VO;|klzG_;1 Ge4lCgEZVS=enL&Ո/6T-7_V'Ojz i~QHM '͎ڂ1EzE^ϼe7D$%c6qAfvQhY\v6os6Jc Y ̍}4 yjv~o%K\1X03 *H  1&$simple-http-server0! *H  1Time 15708090146480T *H E0A0: *H 0) *H  0 C$A~Cۧ-P΢,(lʵCք@^G 26 :3X_gqN%B->h ^Wsp-:qkq֙TqF/_aڱi 0թ3$u7A.'\ Ka[w?x7$a/qF34fW|>It;JqMq$M V5pF~fo|P۔s;ʠc)PGXC{yn E Ϊn+:yPwWJ4HbV֭0!0 +1+Ø/:V+eg <K_ V%Kb.E݉o;6Z*9 bYeZ.&ύ).*tXa,7ږ] ridHy4eMYaQ?>Vf> "nLu\}r!7Cm$d˰\\hѴKXZFQ3SR٣˜-!9p/.~mT7iLX-cI8-=5ʲ鈺5aeܹyIBTJyMOYѿ k)1`AfDGC1IgT5WdoWdboj& ;ϨE#Ra#V{_!1h\1݁79M"PG*%Q;_>5JQLÔȐh>\@?[NJ04R~ivmʷZ}Ŋ7sHtsMK'ge;e' c}>zC([bdu$ONjԫ8k-t3ڡUC6>|]l!#ݜMԸ>͞%Ty4Eg\s3j#;( ?96F-f<)w ilr^`: /{"zQN6:ƭ1@0 *H  1 server0! *H  1Time 15708094235680  *H  0 0  *H 0) *H  0U* nJؼ,+#P zI%?$Rx{Rnʔ^DĠW/Զ~s&"[Ofslc[#^,"*5T~sd w|E$QLߔ4hg4XߒQпp{EQ2UR*x2IwsOF@3Q}DD:?<^ 6aV '$~2+;:4;^XjK.2~uۻє6K붴IC"iKŕ*`!Q,\|&w3/SlS;_Dq7敀ls3/q*H>4 @'_;nc!;W6¿,Vps"r:'\k$ay3g%&!Ho;z_i[kCO|C9Qui@?tk=QT扽`3c FNHcD+g8iݕĮ`cز: _8et QG٣~ᙼ%\k3h'zqgY:s FX48Ujָg/Q8 :&B=d 704#-$E-v"rU Ž,mD_;6d)hN9hrKt^|] @Z [~`gΕă@|q/Vfg:)gC9SkmZXE:j ؚGo7 p4fk#Oļfcb ifit@7:/i(~`ğ^rhOB(DNvpm%xݯA%V+?+ yX*r^ TѠ vJek,g`u֜/} % qdtAB6#L2ΎeJaǫF쁊Fgo Vz!@4j-:85 c۷`t$7pcNo b9A)@*2w/]Tͮp=&=QHWegV#2'\4AcK k.ʦ] .l5wF9$!pN!Vu9αy$=%uzA NtK^Qq mc,5,怘jNѓ0-*7)X=~DjTmrQC8Y h]~st\.s9 ѷ|V~9M`yߏ]ú[=dI 5>G8ȺI0_S WW:Enc" uklmXMgc18 XrL)8p(Q&p?$!p?=lƧdrfV$ >Xʿ?CZx̿fv`z/aTE{vJJkZ`Դzb9-qܖ?{v?;SUɾ]BGk~9b@O>]c( }PKf)9qN<$ Cf[dWR`X|,yp}e$w|ʕ~H,mg0 D\=,ho:M<p U-I%NJcܙ1`>2iH{f_9 IQZ)C4(”DO6npє)6SfQr$ztmẵ2b&cO餡&j/npb& ^1 yXjnU"?,!? &lS/9~+u6 .ThiͬpiιC|f~GW ESf su 碞^OA >qC{8詯'YEṟXZ+ϋ1OJFdi%^çtQ㽮ztrQ@3YU8ܺq9C,AYĩ-)*bRO&վd ac&"XeF=m` |? `hp,w_~'$51vWf/ xTy:>FQE:n̈́~)yIvY]~,I;ٴm5M3<6S!qkegjqmsX_G}Gy USW n-y(E[eHEtD oGptC?W4IP|B&,OXVM3e||8I5fV/kuĊd$w.؍fXNο7G%(ja_pPKRm_!K"^ h~>[}`WjwХP2!wTv!)JgВd~q6q^JB>h-Ͷhng޹&~O=j:EA85KoU|4[f'm./Hkul @eB 7ox'ϙm\3!L&F^(2{Q|Dgk-uVSeܞE{?l- iP1ߘM\-ty0qCS #vFCn?\f8͇ ^m XeW#qv߇`XdPz"2tXlGB/W=`թ"BCTѯ{4 * I欯=HW[0_Vg{4]4SŒToYQ r^:^=XQKFAC.2` &H wj<` ݲ~K8b<1oe\"5me~LчUb.4&+TօMݼƀ5Կc[+UV20b Y0>0!0 + (5df|N(A?` ]dhttpcore5/src/test/resources/test-ssl.txt0100644 0000000 0000000 00000011763 00000250600 017607 0ustar000000000 0000000 # ==================================================================== # 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 # . *** Use Java 11 or older *** == generate test key store with a self signed key --- keytool -genkey \ -keystore test.12 -storepass nopassword \ -keyalg RSA -keysize 2048 \ -alias simple-http-server \ -validity 100000 \ -dname "CN=localhost, OU=Apache HttpComponents, O=Apache Software Foundation" \ -ext SAN="DNS:localhost" --- == generate test key store with a self signed key protected with a key password --- keytool -genkey \ -keystore test-keypasswd.p12 -storepass nopassword \ -keyalg RSA -keysize 2048 \ -alias simple-http-server \ -validity 100000 \ -dname "CN=localhost, OU=Apache HttpComponents, O=Apache Software Foundation" \ -ext SAN="DNS:localhost" --- == generate test CA --- keytool -genkeypair \ -keystore ca.p12 -storepass nopassword \ -keyalg RSA -keysize 2048 \ -alias ca \ -validity 100000 \ -dname "EMAILADDRESS=dev@hc.apache.org, CN=Test CA, OU=HttpComponents Project, O=Apache Software Foundation" \ -ext KeyUsage:critical="keyCertSign" \ -ext BasicConstraints:critical="ca:true" \ -ext SAN="EMAIL:dev@hc.apache.org" --- == export test CA certificate --- keytool -export \ -keystore ca.p12 -storepass nopassword \ -alias ca \ -file test-ca.crt \ -rfc --- == generate test server key --- keytool -genkeypair \ -keystore test-server.p12 -storepass nopassword \ -keyalg RSA -keysize 2048 \ -alias server \ -validity 100000 \ -dname "CN=Test Server, OU=HttpComponents Project, O=Apache Software Foundation" --- == create server certificate signing request --- keytool -certreq \ -keystore test-server.p12 -storepass nopassword \ -alias server \ -file server.csr --- == sign server certificate --- keytool -gencert \ -keystore ca.p12 -storepass nopassword \ -alias ca \ -validity 100000 \ -infile server.csr \ -outfile server.crt \ -ext KeyUsage:critical="digitalSignature,keyEncipherment" \ -ext EKU="serverAuth" \ -ext SAN="DNS:localhost" \ -rfc --- == import CA root certificate and signed server certificate --- keytool -importcert \ -keystore test-server.p12 -storepass nopassword \ -file test-ca.crt \ -alias caroot --- keytool -importcert \ -keystore test-server.p12 -storepass nopassword \ -file server.crt \ -alias server --- == generate client keys --- keytool -genkeypair \ -keystore test-client.p12 -storepass nopassword \ -keyalg RSA -keysize 2048 \ -alias client1 \ -validity 100000 \ -dname "CN=Test Client 1, OU=HttpComponents Project, O=Apache Software Foundation" --- keytool -genkeypair \ -keystore test-client.p12 -storepass nopassword \ -keyalg RSA -keysize 2048 \ -alias client2 \ -validity 100000 \ -dname "CN=Test Client 2, OU=HttpComponents Project, O=Apache Software Foundation" --- == create client certificate signing requests --- keytool -certreq \ -keystore test-client.p12 -storepass nopassword \ -alias client1 \ -file client1.csr --- keytool -certreq \ -keystore test-client.p12 -storepass nopassword \ -alias client2 \ -file client2.csr --- == sign client certificates --- keytool -gencert \ -keystore ca.p12 -storepass nopassword \ -alias ca \ -validity 100000 \ -infile client1.csr \ -outfile client1.crt \ -ext EKU="clientAuth" \ -ext SAN="EMAIL:test-client-1@hc.apache.org" \ -rfc --- keytool -gencert \ -keystore ca.p12 -storepass nopassword \ -alias ca \ -validity 100000 \ -infile client2.csr \ -outfile client2.crt \ -ext EKU="clientAuth" \ -ext SAN="EMAIL:test-client-2@hc.apache.org" \ -rfc --- == import CA root certificate and signed server certificate --- keytool -importcert \ -keystore test-client.p12 -storepass nopassword \ -file test-ca.crt \ -alias caroot --- keytool -importcert \ -keystore test-client.p12 -storepass nopassword \ -file client1.crt \ -alias client1 --- keytool -importcert \ -keystore test-client.p12 -storepass nopassword \ -file client2.crt \ -alias client2 --- httpcore5/src/test/resources/test.p120100644 0000000 0000000 00000005077 00000250600 016574 0ustar000000000 0000000 0 ;0  *H   0 0 *H rn0j0f *H  00) *H  0`4ALI2vP)Thʡ{c'$jTkN|Gh^o#qA+kw|]"ơQ`'^Ja#6"y?1CR CS!Hn _77hJ_G%(`GI?C?¤GS<#9w?_&TXf(qKW9Z~ppy0Sa3/Jnj;QxcKъ^Lbӂ @[\Q"+˻2lt^j~qNSz24T_ )f"wW-P>bȻ`&., tq[S_ԇڍ1E+]Զ]LtdoVZf-@|`T#|nf:D16[y^|`qVC^OV(dQ]gh8!ɊcNCSһK*&'G# }xPc;cv9jT/`x3JG'gO`$γE(lp`Kߡl^ظ:Ⲑ%~7C ^#/oF] :6E//zcBkViEd"vb$)|UF͗%# "ASEȵ24`t: jFrٞ@ D0|aANvlWzz'g]v~&)]cWlCǼ Ū?GdDdɑR8ڄX=/ xpS1ܢN%ߋх"7'o#U^)em:YwfH'ӽ3 !<PNh >\/#+K -{;h+Gʈnם6U<\7-눌C$gJ7wطi??WF)l9οpo}\CAz1 ZW-f!ʰbT 2-=$@8͚#h,Æ:W:6zZDJ$3/FF\&S /+`QU A197 l4e.54{wBJi,1X03 *H  1&$simple-http-server0! *H  1Time 15708083005120T *H E0A0: *H 0) *H  0:Cztr;!K ,Pe4-3m!DwހZBݪa5W ݲF;%C 0F}B2g.2S)V㊲_m qWQ+`cf0Y{ <ӌzDt@lO& CNJ) Ssʗh*R9iQ ]3kZ 7(Kl#GCc/q*~aYHLy+Z5Bpn\-/~cQ5ڴ9IcZ#6EғB&_t3IӼTP,J[ D0c`t2;5F4$uERui&tﳹ74<~jTmseѵipi7 zvc}Q!SrZKgA}j~`dxzMXr ҷ> |N.d .sc?9>1ԛÃmG}[@'v&X)"S@q>dzD 1x$$R{ꯀFt4hApf>r.|KcJ;r 0t!Vtip ѤK/n)( Eiv*0T9BѦ}QlPNKjlYBM y!|ҰL'CSi 0!0 +Hb;`wA|˪,q41xcPdBOڛ2httpcore5-h2/0040755 0000000 0000000 00000000000 00000250600 012012 5ustar000000000 0000000 httpcore5-h2/pom.xml0100644 0000000 0000000 00000007040 00000250600 013325 0ustar000000000 0000000 4.0.0 org.apache.httpcomponents.core5 httpcore5-parent 5.4.3 httpcore5-h2 Apache HttpComponents Core HTTP/2 Apache HttpComponents HTTP/2 Core Components org.apache.httpcomponents.core5.httpcore5.h2 org.apache.httpcomponents.core5 httpcore5 org.conscrypt conscrypt-openjdk-uber true org.junit.jupiter junit-jupiter test org.hamcrest hamcrest test org.mockito mockito-core test org.slf4j slf4j-api test org.apache.logging.log4j log4j-slf4j-impl test org.apache.logging.log4j log4j-core test maven-project-info-reports-plugin false index dependencies dependency-info summary httpcore5-h2/src/0040755 0000000 0000000 00000000000 00000250600 012601 5ustar000000000 0000000 httpcore5-h2/src/main/0040755 0000000 0000000 00000000000 00000250600 013525 5ustar000000000 0000000 httpcore5-h2/src/main/java/0040755 0000000 0000000 00000000000 00000250600 014446 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/0040755 0000000 0000000 00000000000 00000250600 015235 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/apache/0040755 0000000 0000000 00000000000 00000250600 016456 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/0040755 0000000 0000000 00000000000 00000250600 017050 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/0040755 0000000 0000000 00000000000 00000250600 020065 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/0040755 0000000 0000000 00000000000 00000250600 021126 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2ConnectionException.java0100644 0000000 0000000 00000003640 00000250600 026141 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2; import java.io.IOException; import org.apache.hc.core5.util.Args; /** * Signals fatal HTTP/2 protocol violation that renders the actual * HTTP/2 connection unreliable. * * @since 5.0 */ public class H2ConnectionException extends IOException { private static final long serialVersionUID = -2014204317155428658L; private final int code; public H2ConnectionException(final H2Error error, final String message) { super(message); Args.notNull(error, "H2 Error code"); this.code = error.getCode(); } public H2ConnectionException(final int code, final String message) { super(message); this.code = code; } public int getCode() { return code; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2CorruptFrameException.java0100644 0000000 0000000 00000002751 00000250600 026455 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2; import java.io.IOException; /** * Signals corrupt HTTP/2 frame. * * @since 5.0 */ public class H2CorruptFrameException extends IOException { private static final long serialVersionUID = 1L; public H2CorruptFrameException(final String message) { super(message); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2Error.java0100644 0000000 0000000 00000010360 00000250600 023251 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * Errors codes defined by HTTP/2 specification. * * @since 5.0 */ public enum H2Error { /** * Graceful shutdown *

* The associated condition is not as a result of an error. */ NO_ERROR (0x00), /** * Protocol error detected *

* The endpoint detected an unspecific protocol error. This error is for use when a more * specific error code is not available */ PROTOCOL_ERROR (0x01), /** * Implementation fault *

* The endpoint encountered an unexpected internal error. */ INTERNAL_ERROR (0x02), /** * Flow control limits exceeded. *

* The endpoint detected that its peer violated the flow control protocol. */ FLOW_CONTROL_ERROR (0x03), /** * Settings not acknowledged. *

* The endpoint sent a SETTINGS frame, but did not receive a response in a timely manner. */ SETTINGS_TIMEOUT (0x04), /** * Frame received for closed stream. *

* The endpoint received a frame after a stream was half closed. */ STREAM_CLOSED (0x05), /** * Frame size incorrect. *

* The endpoint received a frame with an invalid size. */ FRAME_SIZE_ERROR (0x06), /** * Stream not processed *

* The endpoint refuses the stream prior to performing any application processing. */ REFUSED_STREAM (0x07), /** * Stream canceled. *

* Used by the endpoint to indicate that the stream is no longer needed */ CANCEL (0x08), /** * Compression state not updated. *

* The endpoint is unable to maintain the header compression context for the connection. */ COMPRESSION_ERROR (0x09), /** * TCP connection error. *

* The connection established in response to a CONNECT request was reset or abnormally closed. */ CONNECT_ERROR (0x0a), /** * Processing capacity exceeded. *

* The endpoint detected that its peer is exhibiting a behavior that might be generating * excessive load. */ ENHANCE_YOUR_CALM (0x0b), /** * Negotiated TLS parameters not acceptable. *

* The underlying transport has properties that do not meet minimum security requirements. */ INADEQUATE_SECURITY (0x0c), /** * Use HTTP/1.1 for the request. *

* The endpoint requires that HTTP/1.1 be used instead of HTTP/2. */ HTTP_1_1_REQUIRED (0x0d); int code; H2Error(final int code) { this.code = code; } public int getCode() { return code; } private static final ConcurrentMap MAP_BY_CODE; static { MAP_BY_CODE = new ConcurrentHashMap<>(); for (final H2Error error: values()) { MAP_BY_CODE.putIfAbsent(error.code, error); } } public static H2Error getByCode(final int code) { return MAP_BY_CODE.get(code); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2MessageConverter.java0100644 0000000 0000000 00000004371 00000250600 025441 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2; import java.util.List; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpMessage; /** * Abstract message converter intended to convert from a list of HTTP/2 headers to object * representing an HTTP message and from an object representing an HTTP message to a list * of HTTP/2 headers. * * @param represents {@link HttpMessage} * * @since 5.0 */ public interface H2MessageConverter { /** * Converts given list of HTTP/2 headers to a {@link HttpMessage}. * * @param headers list of HTTP/2 headers * @return HTTP message * @throws HttpException in case of HTTP protocol violation */ T convert(List

headers) throws HttpException; /** * Converts given {@link HttpMessage} to a list of HTTP/2 headers. * * @param message HTTP message * @return list of HTTP/2 headers * @throws HttpException in case of HTTP protocol violation */ List
convert(T message) throws HttpException; } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2PseudoRequestHeaders.java0100644 0000000 0000000 00000003040 00000250600 026261 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2; /** * Request pseudo HTTP headers defined by the HTTP/2 specification. * * @since 5.0 */ public final class H2PseudoRequestHeaders { public static final String METHOD = ":method"; public static final String SCHEME = ":scheme"; public static final String AUTHORITY = ":authority"; public static final String PATH = ":path"; } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2PseudoResponseHeaders.java0100644 0000000 0000000 00000002607 00000250600 026437 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2; /** * Response pseudo HTTP headers defined by the HTTP/2 specification. * * @since 5.0 */ public final class H2PseudoResponseHeaders { public static final String STATUS = ":status"; } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2StreamResetException.java0100644 0000000 0000000 00000004045 00000250600 026300 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2; import org.apache.hc.core5.http.HttpStreamResetException; import org.apache.hc.core5.util.Args; /** * Signals HTTP/2 protocol error that renders the actual HTTP/2 data stream * unreliable. * * @since 5.0 */ public class H2StreamResetException extends HttpStreamResetException { /** * Required for serialization support. * * @see java.io.Serializable */ private static final long serialVersionUID = 4280996898701236013L; private final int code; public H2StreamResetException(final H2Error error, final String message) { super(message); Args.notNull(error, "H2 Error code"); this.code = error.getCode(); } public H2StreamResetException(final int code, final String message) { super(message); this.code = code; } public int getCode() { return code; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2TransportMetrics.java0100644 0000000 0000000 00000002661 00000250600 025510 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2; import org.apache.hc.core5.http.io.HttpTransportMetrics; /** * The point of access to connection statistics. * * @since 5.0 */ public interface H2TransportMetrics extends HttpTransportMetrics { long getFramesTransferred(); } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/HttpVersionPolicy.java0100644 0000000 0000000 00000002514 00000250600 025435 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2; /** * HTTP protocol version policy. * * @since 5.0 */ public enum HttpVersionPolicy { FORCE_HTTP_1, FORCE_HTTP_2, NEGOTIATE } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/package-info.java0100644 0000000 0000000 00000002365 00000250600 024320 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Core HTTP/2 transport component APIs. */ package org.apache.hc.core5.http2; httpcore5-h2/src/main/java/org/apache/hc/core5/http2/config/0040755 0000000 0000000 00000000000 00000250600 022373 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/config/H2Config.java0100644 0000000 0000000 00000021016 00000250600 024632 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.config; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http2.frame.FrameConsts; import org.apache.hc.core5.util.Args; /** * HTTP/2 protocol configuration. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class H2Config { public static final H2Config DEFAULT = custom().build(); public static final H2Config INIT = initial().build(); private final int headerTableSize; private final boolean pushEnabled; private final int maxConcurrentStreams; private final int initialWindowSize; private final int maxFrameSize; private final int maxHeaderListSize; private final boolean compressionEnabled; private final int maxContinuations; H2Config(final int headerTableSize, final boolean pushEnabled, final int maxConcurrentStreams, final int initialWindowSize, final int maxFrameSize, final int maxHeaderListSize, final boolean compressionEnabled, final int maxContinuations) { super(); this.headerTableSize = headerTableSize; this.pushEnabled = pushEnabled; this.maxConcurrentStreams = maxConcurrentStreams; this.initialWindowSize = initialWindowSize; this.maxFrameSize = maxFrameSize; this.maxHeaderListSize = maxHeaderListSize; this.compressionEnabled = compressionEnabled; this.maxContinuations = maxContinuations; } public int getHeaderTableSize() { return headerTableSize; } public boolean isPushEnabled() { return pushEnabled; } public int getMaxConcurrentStreams() { return maxConcurrentStreams; } public int getInitialWindowSize() { return initialWindowSize; } public int getMaxFrameSize() { return maxFrameSize; } public int getMaxHeaderListSize() { return maxHeaderListSize; } public boolean isCompressionEnabled() { return compressionEnabled; } public int getMaxContinuations() { return maxContinuations; } @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("[headerTableSize=").append(this.headerTableSize) .append(", pushEnabled=").append(this.pushEnabled) .append(", maxConcurrentStreams=").append(this.maxConcurrentStreams) .append(", initialWindowSize=").append(this.initialWindowSize) .append(", maxFrameSize=").append(this.maxFrameSize) .append(", maxHeaderListSize=").append(this.maxHeaderListSize) .append(", compressionEnabled=").append(this.compressionEnabled) .append(", maxContinuations=").append(this.maxContinuations) .append("]"); return builder.toString(); } public static H2Config.Builder custom() { return new Builder(); } private static final int INIT_HEADER_TABLE_SIZE = 4096; private static final boolean INIT_ENABLE_PUSH = true; private static final int INIT_MAX_FRAME_SIZE = FrameConsts.MIN_FRAME_SIZE; private static final int INIT_WINDOW_SIZE = 65535; private static final int INIT_CONCURRENT_STREAM = 250; public static H2Config.Builder initial() { return new Builder() .setHeaderTableSize(INIT_HEADER_TABLE_SIZE) .setPushEnabled(INIT_ENABLE_PUSH) .setMaxConcurrentStreams(Integer.MAX_VALUE) // no limit .setMaxFrameSize(INIT_MAX_FRAME_SIZE) .setInitialWindowSize(INIT_WINDOW_SIZE) .setMaxHeaderListSize(Integer.MAX_VALUE); // unlimited } public static H2Config.Builder copy(final H2Config config) { Args.notNull(config, "Connection config"); return new Builder() .setHeaderTableSize(config.getHeaderTableSize()) .setPushEnabled(config.isPushEnabled()) .setMaxConcurrentStreams(config.getMaxConcurrentStreams()) .setInitialWindowSize(config.getInitialWindowSize()) .setMaxFrameSize(config.getMaxFrameSize()) .setMaxHeaderListSize(config.getMaxHeaderListSize()) .setCompressionEnabled(config.isCompressionEnabled()); } public static class Builder { private int headerTableSize; private boolean pushEnabled; private int maxConcurrentStreams; private int initialWindowSize; private int maxFrameSize; private int maxHeaderListSize; private boolean compressionEnabled; private int maxContinuations; Builder() { this.headerTableSize = INIT_HEADER_TABLE_SIZE * 2; this.pushEnabled = INIT_ENABLE_PUSH; this.maxConcurrentStreams = INIT_CONCURRENT_STREAM; this.initialWindowSize = INIT_WINDOW_SIZE; this.maxFrameSize = FrameConsts.MIN_FRAME_SIZE * 4; this.maxHeaderListSize = FrameConsts.MAX_FRAME_SIZE; this.compressionEnabled = true; this.maxContinuations = 100; } public Builder setHeaderTableSize(final int headerTableSize) { Args.notNegative(headerTableSize, "Header table size"); this.headerTableSize = headerTableSize; return this; } public Builder setPushEnabled(final boolean pushEnabled) { this.pushEnabled = pushEnabled; return this; } public Builder setMaxConcurrentStreams(final int maxConcurrentStreams) { Args.positive(maxConcurrentStreams, "Max concurrent streams"); this.maxConcurrentStreams = maxConcurrentStreams; return this; } public Builder setInitialWindowSize(final int initialWindowSize) { Args.positive(initialWindowSize, "Initial window size"); this.initialWindowSize = initialWindowSize; return this; } public Builder setMaxFrameSize(final int maxFrameSize) { this.maxFrameSize = Args.checkRange(maxFrameSize, FrameConsts.MIN_FRAME_SIZE, FrameConsts.MAX_FRAME_SIZE, "Invalid max frame size"); return this; } public Builder setMaxHeaderListSize(final int maxHeaderListSize) { Args.positive(maxHeaderListSize, "Max header list size"); this.maxHeaderListSize = maxHeaderListSize; return this; } public Builder setCompressionEnabled(final boolean compressionEnabled) { this.compressionEnabled = compressionEnabled; return this; } /** * Sets max limit on number of continuations. *

value zero represents no limit

* * @since 5,4 */ public Builder setMaxContinuations(final int maxContinuations) { Args.positive(maxContinuations, "Max continuations"); this.maxContinuations = maxContinuations; return this; } public H2Config build() { return new H2Config( headerTableSize, pushEnabled, maxConcurrentStreams, initialWindowSize, maxFrameSize, maxHeaderListSize, compressionEnabled, maxContinuations); } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/config/H2Param.java0100644 0000000 0000000 00000004616 00000250600 024474 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.config; /** * HTTP/2 protocol parameters. * * @since 5.0 */ public enum H2Param { HEADER_TABLE_SIZE(0x1), ENABLE_PUSH(0x2), MAX_CONCURRENT_STREAMS(0x3), INITIAL_WINDOW_SIZE(0x4), MAX_FRAME_SIZE(0x5), MAX_HEADER_LIST_SIZE(0x6), SETTINGS_NO_RFC7540_PRIORITIES (0x9); int code; H2Param(final int code) { this.code = code; } public int getCode() { return code; } private static final H2Param[] LOOKUP_TABLE; static { int max = 0; for (final H2Param p : H2Param.values()) { if (p.code > max) { max = p.code; } } LOOKUP_TABLE = new H2Param[max + 1]; for (final H2Param p : H2Param.values()) { LOOKUP_TABLE[p.code] = p; } } public static H2Param valueOf(final int code) { if (code < 0 || code >= LOOKUP_TABLE.length) { return null; } return LOOKUP_TABLE[code]; } public static String toString(final int code) { if (code < 0 || code >= LOOKUP_TABLE.length || LOOKUP_TABLE[code] == null) { return Integer.toString(code); } return LOOKUP_TABLE[code].name(); } }httpcore5-h2/src/main/java/org/apache/hc/core5/http2/config/H2Setting.java0100644 0000000 0000000 00000003654 00000250600 025052 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.config; import org.apache.hc.core5.util.Args; /** * HTTP/2 protocol settings. * * @since 5.0 */ public final class H2Setting { private final H2Param param; private final int value; public H2Setting(final H2Param param, final int value) { Args.notNull(param, "Setting parameter"); Args.notNegative(value, "Setting value must be a non-negative value"); this.param = param; this.value = value; } public int getCode() { return param.code; } public int getValue() { return value; } @Override public String toString() { final StringBuilder sb = new StringBuilder().append(param).append(": ").append(value); return sb.toString(); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/config/package-info.java0100644 0000000 0000000 00000002366 00000250600 025566 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Core HTTP/2 configuration APIs. */ package org.apache.hc.core5.http2.config; httpcore5-h2/src/main/java/org/apache/hc/core5/http2/frame/0040755 0000000 0000000 00000000000 00000250600 022220 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/frame/DefaultFrameFactory.java0100644 0000000 0000000 00000005543 00000250600 026756 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.frame; import java.nio.ByteBuffer; import org.apache.hc.core5.util.Args; /** * Default {@link FrameFactory} implementation. * * @since 5.0 */ public class DefaultFrameFactory extends FrameFactory { public static final FrameFactory INSTANCE = new DefaultFrameFactory(); @Override public RawFrame createHeaders(final int streamId, final ByteBuffer payload, final boolean endHeaders, final boolean endStream) { Args.positive(streamId, "Stream id"); final int flags = (endHeaders ? FrameFlag.END_HEADERS.value : 0) | (endStream ? FrameFlag.END_STREAM.value : 0); return new RawFrame(FrameType.HEADERS.getValue(), flags, streamId, payload); } @Override public RawFrame createContinuation(final int streamId, final ByteBuffer payload, final boolean endHeaders) { Args.positive(streamId, "Stream id"); final int flags = endHeaders ? FrameFlag.END_HEADERS.value : 0; return new RawFrame(FrameType.CONTINUATION.getValue(), flags, streamId, payload); } @Override public RawFrame createPushPromise(final int streamId, final ByteBuffer payload, final boolean endHeaders) { Args.positive(streamId, "Stream id"); final int flags = endHeaders ? FrameFlag.END_HEADERS.value : 0; return new RawFrame(FrameType.PUSH_PROMISE.getValue(), flags, streamId, payload); } @Override public RawFrame createData(final int streamId, final ByteBuffer payload, final boolean endStream) { Args.positive(streamId, "Stream id"); final int flags = endStream ? FrameFlag.END_STREAM.value : 0; return new RawFrame(FrameType.DATA.getValue(), flags, streamId, payload); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/frame/Frame.java0100644 0000000 0000000 00000004527 00000250600 024122 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.frame; /** * Abstract HTTP/2 data frame. * * @since 5.0 * * @param frame payload representation. */ public abstract class Frame { private final int type; private final int flags; private final int streamId; public Frame(final int type, final int flags, final int streamId) { this.type = type; this.flags = flags; this.streamId = streamId; } public boolean isType(final FrameType type) { return getType() == type.value; } public boolean isFlagSet(final FrameFlag flag) { return (getFlags() & flag.value) != 0; } public int getType() { return type; } public int getFlags() { return flags; } public int getStreamId() { return streamId; } public abstract T getPayload(); @Override public String toString() { final StringBuilder sb = new StringBuilder("["); sb.append("type=").append(type); sb.append(", flags=").append(flags); sb.append(", streamId=").append(streamId); sb.append(", payoad=").append(getPayload()); sb.append(']'); return sb.toString(); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/frame/FrameConsts.java0100644 0000000 0000000 00000003150 00000250600 025303 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.frame; /** * Values used by HTTP/2 protocol. * * @since 5.0 */ public final class FrameConsts { private FrameConsts() { // Do not allow utility class to be instantiated. } public final static int HEAD_LEN = 9; public final static int MAX_PADDING = 255; public final static int MIN_FRAME_SIZE = 16384; // 2 ^ 14 public final static int MAX_FRAME_SIZE = 16777215; // 2 ^ 24 - 1; } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/frame/FrameFactory.java0100644 0000000 0000000 00000011441 00000250600 025443 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.frame; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http2.H2Error; import org.apache.hc.core5.http2.config.H2Setting; import org.apache.hc.core5.util.Args; /** * Abstract {@link RawFrame} factory that supports standard * HTTP/2 {@link FrameType}s. * * @since 5.0 */ public abstract class FrameFactory { public RawFrame createSettings(final H2Setting... settings) { final ByteBuffer payload = ByteBuffer.allocate(settings.length * 12); for (final H2Setting setting: settings) { payload.putShort((short) setting.getCode()); payload.putInt(setting.getValue()); } payload.flip(); return new RawFrame(FrameType.SETTINGS.getValue(), 0, 0, payload); } public RawFrame createSettingsAck() { return new RawFrame(FrameType.SETTINGS.getValue(), FrameFlag.ACK.getValue(), 0, null); } public RawFrame createResetStream(final int streamId, final H2Error error) { Args.notNull(error, "Error"); return createResetStream(streamId, error.getCode()); } public RawFrame createResetStream(final int streamId, final int code) { Args.positive(streamId, "Stream id"); final ByteBuffer payload = ByteBuffer.allocate(4); payload.putInt(code); payload.flip(); return new RawFrame(FrameType.RST_STREAM.getValue(), 0, streamId, payload); } public RawFrame createPing(final ByteBuffer opaqueData) { Args.notNull(opaqueData, "Opaque data"); Args.check(opaqueData.remaining() == 8, "Opaque data length must be equal 8"); return new RawFrame(FrameType.PING.getValue(), 0, 0, opaqueData); } public RawFrame createPingAck(final ByteBuffer opaqueData) { Args.notNull(opaqueData, "Opaque data"); Args.check(opaqueData.remaining() == 8, "Opaque data length must be equal 8"); return new RawFrame(FrameType.PING.getValue(), FrameFlag.ACK.value, 0, opaqueData); } public RawFrame createGoAway(final int lastStream, final H2Error error, final String message) { Args.notNegative(lastStream, "Last stream id"); final byte[] debugData = message != null ? message.getBytes(StandardCharsets.US_ASCII) : null; final ByteBuffer payload = ByteBuffer.allocate(8 + (debugData != null ? debugData.length : 0)); payload.putInt(lastStream); payload.putInt(error.getCode()); if (debugData != null) { payload.put(debugData); } payload.flip(); return new RawFrame(FrameType.GOAWAY.getValue(), 0, 0, payload); } public abstract RawFrame createHeaders(int streamId, ByteBuffer payload, boolean endHeaders, boolean endStream); public abstract RawFrame createContinuation(int streamId, ByteBuffer payload, boolean endHeaders); public abstract RawFrame createPushPromise(int streamId, ByteBuffer payload, boolean endHeaders); public abstract RawFrame createData(int streamId, ByteBuffer payload, boolean endStream); public RawFrame createWindowUpdate(final int streamId, final int increment) { Args.notNegative(streamId, "Stream id"); Args.positive(increment, "Increment"); final ByteBuffer payload = ByteBuffer.allocate(4); payload.putInt(increment); payload.flip(); return new RawFrame(FrameType.WINDOW_UPDATE.getValue(), 0, streamId, payload); } public RawFrame createPriorityUpdate(final ByteBuffer payload) { // type 0x10, flags 0, streamId 0 (connection control stream) return new RawFrame(FrameType.PRIORITY_UPDATE.getValue(), 0, 0, payload); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/frame/FrameFlag.java0100644 0000000 0000000 00000003324 00000250600 024706 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.frame; /** * Standard HTTP/2 frame flags. * * @since 5.0 */ public enum FrameFlag { END_STREAM(0x01), ACK(0x01), END_HEADERS(0x04), PADDED(0x08), PRIORITY(0x20); final int value; FrameFlag(final int value) { this.value = value; } public int getValue() { return value; } public static int of(final FrameFlag... flags) { int value = 0; for (final FrameFlag flag: flags) { value |= flag.value; } return value; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/frame/FramePrinter.java0100644 0000000 0000000 00000022460 00000250600 025462 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.frame; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Objects; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.Chars; import org.apache.hc.core5.http2.H2Error; import org.apache.hc.core5.http2.config.H2Param; @Internal public final class FramePrinter { public void printFrameInfo(final RawFrame frame, final Appendable appendable) throws IOException { appendable.append("stream ").append(Integer.toString(frame.getStreamId())).append(" frame: "); final FrameType type = FrameType.valueOf(frame.getType()); appendable.append(Objects.toString(type)) .append(" (0x").append(Integer.toHexString(frame.getType())).append("); flags: "); final int flags = frame.getFlags(); if (flags > 0) { switch (type) { case SETTINGS: case PING: if ((flags & FrameFlag.ACK.value) > 0) { appendable.append(FrameFlag.ACK.name()).append(" "); } break; case DATA: if ((flags & FrameFlag.END_STREAM.value) > 0) { appendable.append(FrameFlag.END_STREAM.name()).append(" "); } if ((flags & FrameFlag.PADDED.value) > 0) { appendable.append(FrameFlag.PADDED.name()).append(" "); } break; case HEADERS: if ((flags & FrameFlag.END_STREAM.value) > 0) { appendable.append(FrameFlag.END_STREAM.name()).append(" "); } if ((flags & FrameFlag.END_HEADERS.value) > 0) { appendable.append(FrameFlag.END_HEADERS.name()).append(" "); } if ((flags & FrameFlag.PADDED.value) > 0) { appendable.append(FrameFlag.PADDED.name()).append(" "); } if ((flags & FrameFlag.PRIORITY.value) > 0) { appendable.append(FrameFlag.PRIORITY.name()).append(" "); } break; case PUSH_PROMISE: if ((flags & FrameFlag.END_HEADERS.value) > 0) { appendable.append(FrameFlag.END_HEADERS.name()).append(" "); } if ((flags & FrameFlag.PADDED.value) > 0) { appendable.append(FrameFlag.PADDED.name()).append(" "); } break; case CONTINUATION: if ((flags & FrameFlag.END_HEADERS.value) > 0) { appendable.append(FrameFlag.END_HEADERS.name()).append(" "); } } } appendable.append("(0x").append(Integer.toHexString(flags)).append("); length: ").append(Integer.toString(frame.getLength())); } public void printPayload(final RawFrame frame, final Appendable appendable) throws IOException { final FrameType type = FrameType.valueOf(frame.getType()); final ByteBuffer buf = frame.getPayloadContent(); if (buf != null) { switch (type) { case SETTINGS: if ((buf.remaining() % 6) == 0) { while (buf.hasRemaining()) { final int code = buf.getShort(); final H2Param param = H2Param.valueOf(code); final int value = buf.getInt(); if (param != null) { appendable.append(param.name()); } else { appendable.append("0x").append(Integer.toHexString(code)); } appendable.append(": ").append(Integer.toString(value)).append("\r\n"); } } else { appendable.append("Invalid\r\n"); } break; case RST_STREAM: if (buf.remaining() == 4) { appendable.append("Code "); final int code = buf.getInt(); final H2Error error = H2Error.getByCode(code); if (error != null) { appendable.append(error.name()); } else { appendable.append("0x").append(Integer.toHexString(code)); } appendable.append("\r\n"); } else { appendable.append("Invalid\r\n"); } break; case GOAWAY: if (buf.remaining() >= 8) { final int lastStream = buf.getInt(); appendable.append("Last stream ").append(Integer.toString(lastStream)).append("\r\n"); appendable.append("Code "); final int code2 = buf.getInt(); final H2Error error2 = H2Error.getByCode(code2); if (error2 != null) { appendable.append(error2.name()); } else { appendable.append("0x").append(Integer.toHexString(code2)); } appendable.append("\r\n"); final byte[] tmp = new byte[buf.remaining()]; buf.get(tmp); appendable.append(new String(tmp, StandardCharsets.US_ASCII)); appendable.append("\r\n"); } else { appendable.append("Invalid\r\n"); } break; case WINDOW_UPDATE: if (buf.remaining() == 4) { final int increment = buf.getInt(); appendable.append("Increment ").append(Integer.toString(increment)).append("\r\n"); } else { appendable.append("Invalid\r\n"); } break; case PUSH_PROMISE: if (buf.remaining() > 4) { final int streamId = buf.getInt(); appendable.append("Promised stream ").append(Integer.toString(streamId)).append("\r\n"); printData(buf, appendable); } else { appendable.append("Invalid\r\n"); } break; default: printData(frame.getPayload(), appendable); } } } public void printData(final ByteBuffer data, final Appendable appendable) throws IOException { final ByteBuffer buf = data.duplicate(); final byte[] line = new byte[16]; while (buf.hasRemaining()) { final int chunk = Math.min(buf.remaining(), line.length); buf.get(line, 0, chunk); for (int i = 0; i < chunk; i++) { final char ch = (char) line[i]; if (ch > Chars.SP && ch <= Chars.DEL) { appendable.append(ch); } else if (Character.isWhitespace(ch)) { appendable.append(' '); } else { appendable.append('.'); } } for (int i = chunk; i < 17; i++) { appendable.append(' '); } for (int i = 0; i < chunk; i++) { appendable.append(' '); final int b = line[i] & 0xff; final String s = Integer.toHexString(b); if (s.length() == 1) { appendable.append("0"); } appendable.append(s); } appendable.append("\r\n"); } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/frame/FrameType.java0100644 0000000 0000000 00000005302 00000250600 024754 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.frame; /** * Standard HTTP/2 frame types. * * @since 5.0 */ public enum FrameType { DATA(0x00), HEADERS(0x01), PRIORITY(0x02), RST_STREAM(0x03), SETTINGS(0x04), PUSH_PROMISE(0x05), PING(0x06), GOAWAY(0x07), WINDOW_UPDATE(0x08), CONTINUATION(0x09), PRIORITY_UPDATE(0x10); // 16 final int value; FrameType(final int value) { this.value = value; } public int getValue() { return value; } private static final FrameType[] LOOKUP_TABLE; static { int max = -1; for (final FrameType t : FrameType.values()) { if (t.value > max) { max = t.value; } } LOOKUP_TABLE = new FrameType[max + 1]; for (final FrameType t : FrameType.values()) { LOOKUP_TABLE[t.value] = t; } } public static FrameType valueOf(final int value) { if (value < 0 || value >= LOOKUP_TABLE.length) { return null; } return LOOKUP_TABLE[value]; // may be null for gaps (e.g., 0x0A..0x0F) } public static String toString(final int value) { if (value < 0 || value >= LOOKUP_TABLE.length) { return Integer.toString(value); } final FrameType t = LOOKUP_TABLE[value]; return t != null ? t.name() : Integer.toString(value); } /** Convenience: compare this enum to a raw frame type byte. */ public boolean same(final int rawType) { return this.value == rawType; } }httpcore5-h2/src/main/java/org/apache/hc/core5/http2/frame/RawFrame.java0100644 0000000 0000000 00000004717 00000250600 024575 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.frame; import java.nio.ByteBuffer; /** * HTTP/2 stream frame. * * @since 5.0 */ public final class RawFrame extends Frame { private final ByteBuffer payload; private final int len; public RawFrame(final int type, final int flags, final int streamId, final ByteBuffer payload) { super(type, flags, streamId); this.payload = payload; this.len = payload != null ? payload.remaining() : 0; } public boolean isPadded() { return isFlagSet(FrameFlag.PADDED); } public int getLength() { return len; } public ByteBuffer getPayloadContent() { if (payload != null) { if (isPadded()) { final ByteBuffer dup = payload.duplicate(); if (dup.remaining() == 0) { return null; } final int padding = dup.get() & 0xff; if (padding > dup.remaining()) { return null; } dup.limit(dup.limit() - padding); return dup; } return payload.duplicate(); } return null; } @Override public ByteBuffer getPayload() { return payload != null ? payload.duplicate() : null; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/frame/StreamIdGenerator.java0100644 0000000 0000000 00000004213 00000250600 026437 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.frame; /** * HTTP/2 stream ID generator. * * @since 5.0 */ public interface StreamIdGenerator { int generate(int previousMax); boolean isSameSide(int streamId); StreamIdGenerator ODD = new StreamIdGenerator() { @Override public int generate(final int previousMax) { int i = previousMax + 1; if (i % 2 == 0) { i++; } return i; } @Override public boolean isSameSide(final int streamId) { return (streamId & 1) == 1; } }; StreamIdGenerator EVEN = new StreamIdGenerator() { @Override public int generate(final int previousMax) { int i = previousMax + 1; if (i % 2 == 1) { i++; } return i; } @Override public boolean isSameSide(final int streamId) { return (streamId & 1) == 0; } }; } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/frame/package-info.java0100644 0000000 0000000 00000002362 00000250600 025407 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Core HTTP/2 data frame APIs. */ package org.apache.hc.core5.http2.frame; httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/0040755 0000000 0000000 00000000000 00000250600 022214 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/FifoBuffer.java0100644 0000000 0000000 00000006136 00000250600 025077 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; import org.apache.hc.core5.http.Header; final class FifoBuffer { private HPackHeader[] array; private int head; private int tail; FifoBuffer(final int initialCapacity) { this.array = new HPackHeader[initialCapacity]; this.head = 0; this.tail = 0; } private void expand() { int newcapacity = (array.length + 1) << 1; if (newcapacity < 0) { newcapacity = Integer.MAX_VALUE; } final Header[] oldArray = array; final int len = oldArray.length; final HPackHeader[] newArray = new HPackHeader[newcapacity]; System.arraycopy(oldArray, head, newArray, 0, len - head); System.arraycopy(oldArray, 0, newArray, len - head, head); array = newArray; head = len; tail = 0; } public void clear() { head = 0; tail = 0; } public void addFirst(final HPackHeader header) { array[head++] = header; if (head == array.length) { head = 0; } if (head == tail) { expand(); } } public HPackHeader get(final int index) { int i = head - index - 1; if (i < 0) { i = array.length + i; } return array[i]; } public HPackHeader getFirst() { return array[head > 0 ? head - 1 : array.length - 1]; } public HPackHeader getLast() { return array[tail]; } public HPackHeader removeLast() { final HPackHeader header = array[tail]; if (header != null) { array[tail++] = null; if (tail == array.length) { tail = 0; } } return header; } public int capacity() { return array.length; } public int size() { int i = head - tail; if (i < 0) { i = array.length + i; } return i; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/FifoLinkedList.java0100644 0000000 0000000 00000010451 00000250600 025723 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; import java.util.Objects; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.util.Args; final class FifoLinkedList { private final InternalNode master; private int length; FifoLinkedList() { this.master = new InternalNode(null); this.master.previous = this.master; this.master.next = this.master; } public Header get(final int index) { Args.check(index <= length, "Length %s cannot be greater then index %s ", length, index); Args.notNegative(index, "index"); InternalNode current = master.next; int n = 0; while (current != master) { if (index == n) { return current.header; } current = current.next; n++; } return null; } public int getIndex(final InternalNode node) { final int seqNum = node.seqNum; if (seqNum < 1) { return -1; } return length - (seqNum - master.previous.seqNum) - 1; } public Header getFirst() { return master.next.header; } public Header getLast() { return master.previous.header; } public int size() { return length; } public InternalNode addFirst(final HPackHeader header) { final InternalNode newNode = new InternalNode(header); final InternalNode oldNode = master.next; master.next = newNode; newNode.previous = master; newNode.next = oldNode; oldNode.previous = newNode; newNode.seqNum = oldNode.seqNum + 1; length++; return newNode; } public InternalNode removeLast() { final InternalNode last = master.previous; if (last.header != null) { final InternalNode lastButOne = last.previous; master.previous = lastButOne; lastButOne.next = master; last.previous = null; last.next = null; last.seqNum = 0; length--; return last; } master.seqNum = 0; return null; } public void clear() { master.previous = master; master.next = master; master.seqNum = 0; length = 0; } class InternalNode implements HPackEntry { private final HPackHeader header; private InternalNode previous; private InternalNode next; private int seqNum; InternalNode(final HPackHeader header) { this.header = header; } @Override public HPackHeader getHeader() { return header; } @Override public int getIndex() { return StaticTable.INSTANCE.length() + FifoLinkedList.this.getIndex(this) + 1; } @Override public String toString() { return "[" + Objects.toString(header, "master") + "; seqNum=" + seqNum + "; previous=" + (previous != null ? previous.header : null) + "; next=" + (next != null ? next.header : null) + ']'; } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackDecoder.java0100644 0000000 0000000 00000030046 00000250600 025333 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Objects; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.ByteArrayBuffer; /** * HPACK decoder. * * @since 5.0 */ @Internal public final class HPackDecoder { private static final String UNEXPECTED_EOS = "Unexpected end of HPACK data"; private static final String MAX_LIMIT_EXCEEDED = "Max integer exceeded"; private final InboundDynamicTable dynamicTable; private final ByteArrayBuffer contentBuf; private final CharsetDecoder charsetDecoder; private CharBuffer tmpBuf; private int maxTableSize; private int maxListSize; HPackDecoder(final InboundDynamicTable dynamicTable, final CharsetDecoder charsetDecoder) { this.dynamicTable = Objects.requireNonNull(dynamicTable); this.contentBuf = new ByteArrayBuffer(256); this.charsetDecoder = charsetDecoder; this.maxTableSize = this.dynamicTable.getMaxSize(); this.maxListSize = Integer.MAX_VALUE; } HPackDecoder(final InboundDynamicTable dynamicTable, final Charset charset) { this(dynamicTable, charset != null && !StandardCharsets.US_ASCII.equals(charset) ? charset.newDecoder() : null); } public HPackDecoder(final int maxTableSize, final Charset charset) { this(new InboundDynamicTable(maxTableSize), charset); } public HPackDecoder(final int maxTableSize, final CharsetDecoder charsetDecoder) { this(new InboundDynamicTable(maxTableSize), charsetDecoder); } static int readByte(final ByteBuffer src) throws HPackException { if (!src.hasRemaining()) { throw new HPackException(UNEXPECTED_EOS); } return src.get() & 0xff; } static int peekByte(final ByteBuffer src) throws HPackException { if (!src.hasRemaining()) { throw new HPackException(UNEXPECTED_EOS); } final int pos = src.position(); final int b = src.get() & 0xff; src.position(pos); return b; } static int decodeInt(final ByteBuffer src, final int n) throws HPackException { final int nbits = 0xff >>> (8 - n); int value = readByte(src) & nbits; if (value < nbits) { return value; } int m = 0; while (m < 32) { final int b = readByte(src); if ((b & 0x80) != 0) { value += (b & 0x7f) << m; m += 7; } else { if (m == 28 && (b & 0xf8) != 0) { break; } value += b << m; return value; } } throw new HPackException(MAX_LIMIT_EXCEEDED); } static void decodePlainString(final ByteArrayBuffer buffer, final ByteBuffer src) throws HPackException { final int strLen = decodeInt(src, 7); final int remaining = src.remaining(); if (strLen > remaining) { throw new HPackException(UNEXPECTED_EOS); } final int originalLimit = src.limit(); src.limit(originalLimit - (remaining - strLen)); buffer.append(src); src.limit(originalLimit); } static void decodeHuffman(final ByteArrayBuffer buffer, final ByteBuffer src) throws HPackException { final int strLen = decodeInt(src, 7); if (strLen > src.remaining()) { throw new HPackException(UNEXPECTED_EOS); } final int limit = src.limit(); src.limit(src.position() + strLen); Huffman.DECODER.decode(buffer, src); src.limit(limit); } void decodeString(final ByteArrayBuffer buffer, final ByteBuffer src) throws HPackException { final int firstByte = peekByte(src); if ((firstByte & 0x80) == 0x80) { decodeHuffman(buffer, src); } else { decodePlainString(buffer, src); } } int getTmpBufSize() { return tmpBuf == null ? 0 : tmpBuf.capacity(); } private void clearState() { if (this.tmpBuf != null) { this.tmpBuf.clear(); } if (this.charsetDecoder != null) { this.charsetDecoder.reset(); } this.contentBuf.clear(); } private void expandCapacity(final int capacity) { final CharBuffer previous = this.tmpBuf; this.tmpBuf = CharBuffer.allocate(capacity); previous.flip(); this.tmpBuf.put(previous); } private void ensureCapacity(final int extra) { if (this.tmpBuf == null) { this.tmpBuf = CharBuffer.allocate(Math.max(256, extra)); } final int requiredCapacity = this.tmpBuf.position() + extra; if (requiredCapacity > this.tmpBuf.capacity()) { expandCapacity(requiredCapacity); } } int decodeString(final ByteBuffer src, final StringBuilder buf) throws HPackException, CharacterCodingException { clearState(); decodeString(this.contentBuf, src); final int binaryLen = this.contentBuf.length(); if (binaryLen == 0) { return 0; } if (this.charsetDecoder == null) { buf.ensureCapacity(binaryLen); for (int i = 0; i < binaryLen; i++) { buf.append((char) (this.contentBuf.byteAt(i) & 0xff)); } } else { final ByteBuffer in = ByteBuffer.wrap(this.contentBuf.array(), 0, binaryLen); while (in.hasRemaining()) { ensureCapacity(in.remaining()); final CoderResult result = this.charsetDecoder.decode(in, this.tmpBuf, true); if (result.isError()) { result.throwException(); } } ensureCapacity(8); final CoderResult result = this.charsetDecoder.flush(this.tmpBuf); if (result.isError()) { result.throwException(); } this.tmpBuf.flip(); buf.append(this.tmpBuf); } return binaryLen; } HPackHeader decodeLiteralHeader( final ByteBuffer src, final HPackRepresentation representation) throws HPackException, CharacterCodingException { final int n = representation == HPackRepresentation.WITH_INDEXING ? 6 : 4; final int index = decodeInt(src, n); final String name; final int nameLen; if (index == 0) { final StringBuilder buf = new StringBuilder(); nameLen = decodeString(src, buf); name = buf.toString(); } else { final HPackHeader existing = this.dynamicTable.getHeader(index); if (existing == null) { throw new HPackException("Invalid header index"); } name = existing.getName(); nameLen = existing.getNameLen(); } final StringBuilder buf = new StringBuilder(); final int valueLen = decodeString(src, buf); final String value = buf.toString(); final HPackHeader header = new HPackHeader(name, nameLen, value, valueLen, representation == HPackRepresentation.NEVER_INDEXED); if (representation == HPackRepresentation.WITH_INDEXING) { this.dynamicTable.add(header); } return header; } HPackHeader decodeIndexedHeader(final ByteBuffer src) throws HPackException { final int index = decodeInt(src, 7); final HPackHeader existing = this.dynamicTable.getHeader(index); if (existing == null) { throw new HPackException("Invalid header index"); } return existing; } public Header decodeHeader(final ByteBuffer src) throws HPackException { final HPackHeader header = decodeHPackHeader(src); return header != null ? new BasicHeader(header.getName(), header.getValue(), header.isSensitive()) : null; } HPackHeader decodeHPackHeader(final ByteBuffer src) throws HPackException { try { while (src.hasRemaining()) { final int b = peekByte(src); if ((b & 0x80) == 0x80) { return decodeIndexedHeader(src); } else if ((b & 0xc0) == 0x40) { return decodeLiteralHeader(src, HPackRepresentation.WITH_INDEXING); } else if ((b & 0xf0) == 0x00) { return decodeLiteralHeader(src, HPackRepresentation.WITHOUT_INDEXING); } else if ((b & 0xf0) == 0x10) { return decodeLiteralHeader(src, HPackRepresentation.NEVER_INDEXED); } else if ((b & 0xe0) == 0x20) { final int maxSize = decodeInt(src, 5); if (maxSize > this.maxTableSize) { throw new HPackException("Requested dynamic header table size exceeds maximum size: " + maxSize); } this.dynamicTable.setMaxSize(maxSize); } else { throw new HPackException("Unexpected header first byte: 0x" + Integer.toHexString(b)); } } return null; } catch (final CharacterCodingException ex) { throw new HPackException(ex.getMessage(), ex); } } public List
decodeHeaders(final ByteBuffer src) throws HPackException { final boolean enforceSizeLimit = maxListSize < Integer.MAX_VALUE; int listSize = 0; final List
list = new ArrayList<>(); while (src.hasRemaining()) { final HPackHeader header = decodeHPackHeader(src); if (header == null) { break; } if (enforceSizeLimit) { listSize += header.getTotalSize(); if (listSize >= maxListSize) { throw new HeaderListConstraintException("Maximum header list size exceeded"); } } list.add(new BasicHeader(header.getName(), header.getValue(), header.isSensitive())); } return list; } public int getMaxTableSize() { return this.maxTableSize; } public void setMaxTableSize(final int maxTableSize) { Args.notNegative(maxTableSize, "Max table size"); this.maxTableSize = maxTableSize; this.dynamicTable.setMaxSize(Math.min(this.dynamicTable.getMaxSize(), maxTableSize)); } public int getMaxListSize() { return maxListSize; } public void setMaxListSize(final int maxListSize) { Args.notNegative(maxListSize, "Max list size"); this.maxListSize = maxListSize; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackEncoder.java0100644 0000000 0000000 00000031466 00000250600 025354 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Objects; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.ByteArrayBuffer; /** * HPACK encoder. * * @since 5.0 */ @Internal public final class HPackEncoder { private final OutboundDynamicTable dynamicTable; private final ByteArrayBuffer huffmanBuf; private final CharsetEncoder charsetEncoder; private ByteBuffer tmpBuf; private int maxTableSize; HPackEncoder(final OutboundDynamicTable dynamicTable, final CharsetEncoder charsetEncoder) { this.dynamicTable = Objects.requireNonNull(dynamicTable); this.huffmanBuf = new ByteArrayBuffer(128); this.charsetEncoder = charsetEncoder; this.maxTableSize = this.dynamicTable.getMaxSize(); } HPackEncoder(final OutboundDynamicTable dynamicTable, final Charset charset) { this(dynamicTable, charset != null && !StandardCharsets.US_ASCII.equals(charset) ? charset.newEncoder() : null); } public HPackEncoder(final int maxTableSize, final Charset charset) { this(new OutboundDynamicTable(maxTableSize), charset); } public HPackEncoder(final int maxTableSize, final CharsetEncoder charsetEncoder) { this(new OutboundDynamicTable(maxTableSize), charsetEncoder); } static void encodeInt(final ByteArrayBuffer dst, final int n, final int i, final int mask) { final int nbits = 0xFF >>> (8 - n); int value = i; if (value < nbits) { dst.append(i | mask); } else { dst.append(nbits | mask); value -= nbits; while (value >= 0x80) { dst.append((value & 0x7F) | 0x80); value >>>= 7; } dst.append(value); } } static void encodeHuffman(final ByteArrayBuffer dst, final ByteBuffer src) { Huffman.ENCODER.encode(dst, src); } void encodeString(final ByteArrayBuffer dst, final ByteBuffer src, final boolean huffman) { final int strLen = src.remaining(); if (huffman) { this.huffmanBuf.clear(); this.huffmanBuf.ensureCapacity(strLen); Huffman.ENCODER.encode(this.huffmanBuf, src); dst.ensureCapacity(this.huffmanBuf.length() + 8); encodeInt(dst, 7, this.huffmanBuf.length(), 0x80); dst.append(this.huffmanBuf.array(), 0, this.huffmanBuf.length()); } else { dst.ensureCapacity(strLen + 8); encodeInt(dst, 7, strLen, 0x0); dst.append(src); } } private void clearState() { if (this.tmpBuf != null) { this.tmpBuf.clear(); } if (this.charsetEncoder != null) { this.charsetEncoder.reset(); } } private void expandCapacity(final int capacity) { final ByteBuffer previous = this.tmpBuf; this.tmpBuf = ByteBuffer.allocate(capacity); previous.flip(); this.tmpBuf.put(previous); } private void ensureCapacity(final int extra) { if (this.tmpBuf == null) { this.tmpBuf = ByteBuffer.allocate(Math.max(256, extra)); } final int requiredCapacity = this.tmpBuf.remaining() + extra; if (requiredCapacity > this.tmpBuf.capacity()) { expandCapacity(requiredCapacity); } } int encodeString( final ByteArrayBuffer dst, final CharSequence charSequence, final int off, final int len, final boolean huffman) throws CharacterCodingException { clearState(); if (this.charsetEncoder == null) { if (huffman) { this.huffmanBuf.clear(); this.huffmanBuf.ensureCapacity(len); Huffman.ENCODER.encode(this.huffmanBuf, charSequence, off, len); dst.ensureCapacity(this.huffmanBuf.length() + 8); encodeInt(dst, 7, this.huffmanBuf.length(), 0x80); dst.append(this.huffmanBuf.array(), 0, this.huffmanBuf.length()); } else { dst.ensureCapacity(len + 8); encodeInt(dst, 7, len, 0x0); for (int i = 0; i < len; i++) { dst.append(charSequence.charAt(off + i)); } } return len; } if (charSequence.length() > 0) { final CharBuffer in = CharBuffer.wrap(charSequence, off, len); while (in.hasRemaining()) { ensureCapacity((int) (in.remaining() * this.charsetEncoder.averageBytesPerChar()) + 8); final CoderResult result = this.charsetEncoder.encode(in, this.tmpBuf, true); if (result.isError()) { result.throwException(); } } ensureCapacity(8); final CoderResult result = this.charsetEncoder.flush(this.tmpBuf); if (result.isError()) { result.throwException(); } } this.tmpBuf.flip(); final int binaryLen = this.tmpBuf.remaining(); encodeString(dst, this.tmpBuf, huffman); return binaryLen; } int encodeString(final ByteArrayBuffer dst, final String s, final boolean huffman) throws CharacterCodingException { return encodeString(dst, s, 0, s.length(), huffman); } void encodeLiteralHeader( final ByteArrayBuffer dst, final HPackEntry existing, final Header header, final HPackRepresentation representation, final boolean useHuffman) throws CharacterCodingException { encodeLiteralHeader(dst, existing, header.getName(), header.getValue(), header.isSensitive(), representation, useHuffman); } void encodeLiteralHeader( final ByteArrayBuffer dst, final HPackEntry existing, final String key, final String value, final boolean sensitive, final HPackRepresentation representation, final boolean useHuffman) throws CharacterCodingException { final int n; final int mask; switch (representation) { case WITH_INDEXING: mask = 0x40; n = 6; break; case WITHOUT_INDEXING: mask = 0x00; n = 4; break; case NEVER_INDEXED: mask = 0x10; n = 4; break; default: throw new IllegalStateException("Unexpected value: " + representation); } final int index = existing != null ? existing.getIndex() : 0; final int nameLen; if (index <= 0) { encodeInt(dst, n, 0, mask); nameLen = encodeString(dst, key, useHuffman); } else { encodeInt(dst, n, index, mask); nameLen = existing.getHeader().getNameLen(); } final int valueLen = encodeString(dst, value != null ? value : "", useHuffman); if (representation == HPackRepresentation.WITH_INDEXING) { dynamicTable.add(new HPackHeader(key, nameLen, value, valueLen, sensitive)); } } void encodeIndex(final ByteArrayBuffer dst, final int index) { encodeInt(dst, 7, index, 0x80); } private int findFullMatch(final List entries, final String value) { if (entries == null || entries.isEmpty()) { return 0; } for (int i = 0; i < entries.size(); i++) { final HPackEntry entry = entries.get(i); if (Objects.equals(value, entry.getHeader().getValue())) { return entry.getIndex(); } } return 0; } void encodeHeader( final ByteArrayBuffer dst, final Header header, final boolean noIndexing, final boolean useHuffman) throws CharacterCodingException { encodeHeader(dst, header.getName(), header.getValue(), header.isSensitive(), noIndexing, useHuffman); } void encodeHeader( final ByteArrayBuffer dst, final String name, final String value, final boolean sensitive, final boolean noIndexing, final boolean useHuffman) throws CharacterCodingException { //send receiver the updated dynamic table size if (maxTableSize != this.dynamicTable.getMaxSize()) { encodeInt(dst, 5, maxTableSize, 0x20); this.dynamicTable.setMaxSize(maxTableSize); } final HPackRepresentation representation; if (sensitive) { representation = HPackRepresentation.NEVER_INDEXED; } else if (noIndexing) { representation = HPackRepresentation.WITHOUT_INDEXING; } else { representation = HPackRepresentation.WITH_INDEXING; } final List staticEntries = StaticTable.INSTANCE.getByName(name); if (representation == HPackRepresentation.WITH_INDEXING) { // Try to find full match and encode as as index final int staticIndex = findFullMatch(staticEntries, value); if (staticIndex > 0) { encodeIndex(dst, staticIndex); return; } final List dynamicEntries = dynamicTable.getByName(name); final int dynamicIndex = findFullMatch(dynamicEntries, value); if (dynamicIndex > 0) { encodeIndex(dst, dynamicIndex); return; } } // Encode as literal HPackEntry existing = null; if (staticEntries != null && !staticEntries.isEmpty()) { existing = staticEntries.get(0); } else { final List dynamicEntries = dynamicTable.getByName(name); if (dynamicEntries != null && !dynamicEntries.isEmpty()) { existing = dynamicEntries.get(0); } } encodeLiteralHeader(dst, existing, name, value, sensitive, representation, useHuffman); } void encodeHeaders( final ByteArrayBuffer dst, final List headers, final boolean noIndexing, final boolean useHuffman) throws CharacterCodingException { for (int i = 0; i < headers.size(); i++) { encodeHeader(dst, headers.get(i), noIndexing, useHuffman); } } public void encodeHeader( final ByteArrayBuffer dst, final Header header) throws CharacterCodingException { Args.notNull(dst, "ByteArrayBuffer"); Args.notNull(header, "Header"); encodeHeader(dst, header.getName(), header.getValue(), header.isSensitive()); } public void encodeHeader( final ByteArrayBuffer dst, final String name, final String value, final boolean sensitive) throws CharacterCodingException { Args.notNull(dst, "ByteArrayBuffer"); Args.notEmpty(name, "Header name"); encodeHeader(dst, name, value, sensitive, false, true); } public void encodeHeaders( final ByteArrayBuffer dst, final List headers, final boolean useHuffman) throws CharacterCodingException { Args.notNull(dst, "ByteArrayBuffer"); Args.notEmpty(headers, "Header list"); encodeHeaders(dst, headers, false, useHuffman); } public int getMaxTableSize() { return this.maxTableSize; } public void setMaxTableSize(final int maxTableSize) { Args.notNegative(maxTableSize, "Max table size"); this.maxTableSize = maxTableSize; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackEntry.java0100644 0000000 0000000 00000002430 00000250600 025063 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; interface HPackEntry { int getIndex(); HPackHeader getHeader(); } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackException.java0100644 0000000 0000000 00000003145 00000250600 025724 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; import org.apache.hc.core5.http.HttpException; /** * Signals HPACK protocol violation. * * @since 5.0 */ public class HPackException extends HttpException { private static final long serialVersionUID = 1L; public HPackException(final String message) { super(message); } public HPackException(final String message, final Exception cause) { super(message, cause); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackHeader.java0100644 0000000 0000000 00000005715 00000250600 025163 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; import org.apache.hc.core5.http.Header; /** * Internal HPack header representation that also contains binary length of * header name and header value. */ final class HPackHeader implements Header { static private final int ENTRY_SIZE_OVERHEAD = 32; private final String name; private final int nameLen; private final String value; private final int valueLen; private final boolean sensitive; HPackHeader(final String name, final int nameLen, final String value, final int valueLen, final boolean sensitive) { this.name = name; this.nameLen = nameLen; this.value = value; this.valueLen = valueLen; this.sensitive = sensitive; } HPackHeader(final String name, final String value, final boolean sensitive) { this(name, name.length(), value, value.length(), sensitive); } HPackHeader(final String name, final String value) { this(name, value, false); } HPackHeader(final Header header) { this(header.getName(), header.getValue(), header.isSensitive()); } @Override public String getName() { return name; } public int getNameLen() { return nameLen; } @Override public String getValue() { return value; } public int getValueLen() { return valueLen; } @Override public boolean isSensitive() { return sensitive; } public int getTotalSize() { return nameLen + valueLen + ENTRY_SIZE_OVERHEAD; } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append(this.name).append(": "); if (this.value != null) { buf.append(this.value); } return buf.toString(); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackRepresentation.java0100644 0000000 0000000 00000002426 00000250600 026771 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; enum HPackRepresentation { WITH_INDEXING, WITHOUT_INDEXING, NEVER_INDEXED }httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HeaderListConstraintException.java0100644 0000000 0000000 00000003401 00000250600 031022 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; /** * Signals a header list constraint violation. * * @since 5.0 */ public class HeaderListConstraintException extends HPackException { /** * Required for serialization support. * * @see java.io.Serializable */ private static final long serialVersionUID = 9130981983188889920L; /** * Creates a HeaderListConstraintException with the specified detail message. * * @param message The exception detail message */ public HeaderListConstraintException(final String message) { super(message); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/Huffman.java0100644 0000000 0000000 00000020206 00000250600 024440 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; import org.apache.hc.core5.annotation.Internal; /** * This Huffman codec implementation has been derived from Twitter HPack project * (https://github.com/twitter/hpack) */ @Internal public final class Huffman { private Huffman() { // Do not allow utility class to be instantiated. } static final int[] CODES = { 0x1ff8, 0x7fffd8, 0xfffffe2, 0xfffffe3, 0xfffffe4, 0xfffffe5, 0xfffffe6, 0xfffffe7, 0xfffffe8, 0xffffea, 0x3ffffffc, 0xfffffe9, 0xfffffea, 0x3ffffffd, 0xfffffeb, 0xfffffec, 0xfffffed, 0xfffffee, 0xfffffef, 0xffffff0, 0xffffff1, 0xffffff2, 0x3ffffffe, 0xffffff3, 0xffffff4, 0xffffff5, 0xffffff6, 0xffffff7, 0xffffff8, 0xffffff9, 0xffffffa, 0xffffffb, 0x14, 0x3f8, 0x3f9, 0xffa, 0x1ff9, 0x15, 0xf8, 0x7fa, 0x3fa, 0x3fb, 0xf9, 0x7fb, 0xfa, 0x16, 0x17, 0x18, 0x0, 0x1, 0x2, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x5c, 0xfb, 0x7ffc, 0x20, 0xffb, 0x3fc, 0x1ffa, 0x21, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0xfc, 0x73, 0xfd, 0x1ffb, 0x7fff0, 0x1ffc, 0x3ffc, 0x22, 0x7ffd, 0x3, 0x23, 0x4, 0x24, 0x5, 0x25, 0x26, 0x27, 0x6, 0x74, 0x75, 0x28, 0x29, 0x2a, 0x7, 0x2b, 0x76, 0x2c, 0x8, 0x9, 0x2d, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7ffe, 0x7fc, 0x3ffd, 0x1ffd, 0xffffffc, 0xfffe6, 0x3fffd2, 0xfffe7, 0xfffe8, 0x3fffd3, 0x3fffd4, 0x3fffd5, 0x7fffd9, 0x3fffd6, 0x7fffda, 0x7fffdb, 0x7fffdc, 0x7fffdd, 0x7fffde, 0xffffeb, 0x7fffdf, 0xffffec, 0xffffed, 0x3fffd7, 0x7fffe0, 0xffffee, 0x7fffe1, 0x7fffe2, 0x7fffe3, 0x7fffe4, 0x1fffdc, 0x3fffd8, 0x7fffe5, 0x3fffd9, 0x7fffe6, 0x7fffe7, 0xffffef, 0x3fffda, 0x1fffdd, 0xfffe9, 0x3fffdb, 0x3fffdc, 0x7fffe8, 0x7fffe9, 0x1fffde, 0x7fffea, 0x3fffdd, 0x3fffde, 0xfffff0, 0x1fffdf, 0x3fffdf, 0x7fffeb, 0x7fffec, 0x1fffe0, 0x1fffe1, 0x3fffe0, 0x1fffe2, 0x7fffed, 0x3fffe1, 0x7fffee, 0x7fffef, 0xfffea, 0x3fffe2, 0x3fffe3, 0x3fffe4, 0x7ffff0, 0x3fffe5, 0x3fffe6, 0x7ffff1, 0x3ffffe0, 0x3ffffe1, 0xfffeb, 0x7fff1, 0x3fffe7, 0x7ffff2, 0x3fffe8, 0x1ffffec, 0x3ffffe2, 0x3ffffe3, 0x3ffffe4, 0x7ffffde, 0x7ffffdf, 0x3ffffe5, 0xfffff1, 0x1ffffed, 0x7fff2, 0x1fffe3, 0x3ffffe6, 0x7ffffe0, 0x7ffffe1, 0x3ffffe7, 0x7ffffe2, 0xfffff2, 0x1fffe4, 0x1fffe5, 0x3ffffe8, 0x3ffffe9, 0xffffffd, 0x7ffffe3, 0x7ffffe4, 0x7ffffe5, 0xfffec, 0xfffff3, 0xfffed, 0x1fffe6, 0x3fffe9, 0x1fffe7, 0x1fffe8, 0x7ffff3, 0x3fffea, 0x3fffeb, 0x1ffffee, 0x1ffffef, 0xfffff4, 0xfffff5, 0x3ffffea, 0x7ffff4, 0x3ffffeb, 0x7ffffe6, 0x3ffffec, 0x3ffffed, 0x7ffffe7, 0x7ffffe8, 0x7ffffe9, 0x7ffffea, 0x7ffffeb, 0xffffffe, 0x7ffffec, 0x7ffffed, 0x7ffffee, 0x7ffffef, 0x7fffff0, 0x3ffffee, 0x3fffffff }; static final byte[] LENGTHS = { 13, 23, 28, 28, 28, 28, 28, 28, 28, 24, 30, 28, 28, 30, 28, 28, 28, 28, 28, 28, 28, 28, 30, 28, 28, 28, 28, 28, 28, 28, 28, 28, 6, 10, 10, 12, 13, 6, 8, 11, 10, 10, 8, 11, 8, 6, 6, 6, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 8, 15, 6, 12, 10, 13, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 7, 8, 13, 19, 13, 14, 6, 15, 5, 6, 5, 6, 5, 6, 6, 6, 5, 7, 7, 6, 6, 6, 5, 6, 7, 6, 5, 5, 6, 7, 7, 7, 7, 7, 15, 11, 14, 13, 28, 20, 22, 20, 20, 22, 22, 22, 23, 22, 23, 23, 23, 23, 23, 24, 23, 24, 24, 22, 23, 24, 23, 23, 23, 23, 21, 22, 23, 22, 23, 23, 24, 22, 21, 20, 22, 22, 23, 23, 21, 23, 22, 22, 24, 21, 22, 23, 23, 21, 21, 22, 21, 23, 22, 23, 23, 20, 22, 22, 22, 23, 22, 22, 23, 26, 26, 20, 19, 22, 23, 22, 25, 26, 26, 26, 27, 27, 26, 24, 25, 19, 21, 26, 27, 27, 26, 27, 24, 21, 21, 26, 26, 28, 27, 27, 27, 20, 24, 20, 21, 22, 21, 21, 23, 22, 22, 25, 25, 24, 24, 26, 23, 26, 27, 26, 26, 27, 27, 27, 27, 27, 28, 27, 27, 27, 27, 27, 26, 30 }; static final int EOS = 256; static final HuffmanEncoder ENCODER = new HuffmanEncoder(CODES, LENGTHS); static final HuffmanDecoder DECODER = new HuffmanDecoder(CODES, LENGTHS); } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HuffmanDecoder.java0100644 0000000 0000000 00000010326 00000250600 025730 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; import java.nio.ByteBuffer; import org.apache.hc.core5.util.ByteArrayBuffer; /** * This Huffman codec implementation has been derived from Twitter HPack project * (https://github.com/twitter/hpack) */ final class HuffmanDecoder { private final HuffmanNode root; HuffmanDecoder(final int[] codes, final byte[] lengths) { root = buildTree(codes, lengths); } void decode(final ByteArrayBuffer out, final ByteBuffer src) throws HPackException { HuffmanNode node = this.root; int current = 0; int bits = 0; while (src.hasRemaining()) { final int b = src.get() & 0xFF; current = (current << 8) | b; bits += 8; while (bits >= 8) { final int c = (current >>> (bits - 8)) & 0xFF; node = node.getChild(c); bits -= node.getBits(); if (node.isTerminal()) { if (node.getSymbol() == Huffman.EOS) { throw new HPackException("EOS decoded"); } out.append(node.getSymbol()); node = root; } } } while (bits > 0) { final int c = (current << (8 - bits)) & 0xFF; node = node.getChild(c); if (node.isTerminal() && node.getBits() <= bits) { bits -= node.getBits(); out.append(node.getSymbol()); node = this.root; } else { break; } } // Section 5.2. String Literal Representation // Padding not corresponding to the most significant bits of the code // for the EOS symbol (0xFF) MUST be treated as a decoding error. final int mask = (1 << bits) - 1; if ((current & mask) != mask) { throw new HPackException("Invalid padding"); } } private static HuffmanNode buildTree(final int[] codes, final byte[] lengths) { final HuffmanNode root = new HuffmanNode(); for (int symbol = 0; symbol < codes.length; symbol++) { final int code = codes[symbol]; int length = lengths[symbol]; HuffmanNode current = root; while (length > 8) { if (current.isTerminal()) { throw new IllegalStateException("Invalid Huffman code: prefix not unique"); } length -= 8; final int i = (code >>> length) & 0xFF; if (!current.hasChild(i)) { current.setChild(i, new HuffmanNode()); } current = current.getChild(i); } final HuffmanNode terminal = new HuffmanNode(symbol, length); final int shift = 8 - length; final int start = (code << shift) & 0xFF; final int end = 1 << shift; for (int i = start; i < start + end; i++) { current.setChild(i, terminal); } } return root; } }httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HuffmanEncoder.java0100644 0000000 0000000 00000005723 00000250600 025747 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; import java.nio.ByteBuffer; import org.apache.hc.core5.util.ByteArrayBuffer; /** * This Huffman codec implementation has been derived from Twitter HPack project * (https://github.com/twitter/hpack) */ final class HuffmanEncoder { private final int[] codes; private final byte[] lengths; HuffmanEncoder(final int[] codes, final byte[] lengths) { this.codes = codes; this.lengths = lengths; } void encode(final ByteArrayBuffer out, final ByteBuffer src) { long current = 0; int n = 0; while (src.hasRemaining()) { final int b = src.get() & 0xFF; final int code = codes[b]; final int nbits = lengths[b]; current <<= nbits; current |= code; n += nbits; while (n >= 8) { n -= 8; out.append((int)(current >> n)); } } if (n > 0) { current <<= 8 - n; current |= 0xFF >>> n; // this should be EOS symbol out.append((int) current); } } void encode(final ByteArrayBuffer out, final CharSequence src, final int off, final int len) { long current = 0; int n = 0; for (int i = 0; i < len; i++) { final int b = src.charAt(off + i) & 0xFF; final int code = codes[b]; final int nbits = lengths[b]; current <<= nbits; current |= code; n += nbits; while (n >= 8) { n -= 8; out.append((int)(current >> n)); } } if (n > 0) { current <<= 8 - n; current |= 0xFF >>> n; // this should be EOS symbol out.append((int) current); } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HuffmanNode.java0100644 0000000 0000000 00000005145 00000250600 025253 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; import java.util.Arrays; import org.apache.hc.core5.util.Asserts; /** * This Huffman codec implementation has been derived from Twitter HPack project * (https://github.com/twitter/hpack) */ final class HuffmanNode { private final int symbol; private final int bits; private final HuffmanNode[] children; HuffmanNode() { this.symbol = 0; this.bits = 8; this.children = new HuffmanNode[256]; } HuffmanNode(final int symbol, final int bits) { this.symbol = symbol; this.bits = bits; this.children = null; } public int getBits() { return this.bits; } public int getSymbol() { return this.symbol; } public boolean hasChild(final int index) { return this.children != null && this.children[index] != null; } public HuffmanNode getChild(final int index) { return this.children != null ? this.children[index] : null; } void setChild(final int index, final HuffmanNode child) { Asserts.notNull(this.children, "Children nodes"); this.children[index] = child; } public boolean isTerminal() { return this.children == null; } @Override public String toString() { return "[" + "symbol=" + symbol + ", bits=" + bits + ", children=" + Arrays.toString(children) + ']'; } }httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/InboundDynamicTable.java0100644 0000000 0000000 00000007061 00000250600 026733 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; final class InboundDynamicTable { private final StaticTable staticTable; private final FifoBuffer headers; private int maxSize; private int currentSize; InboundDynamicTable(final int maxSize, final StaticTable staticTable) { this.staticTable = staticTable; this.headers = new FifoBuffer(256); this.maxSize = maxSize; this.currentSize = 0; } InboundDynamicTable(final int maxSize) { this(maxSize, StaticTable.INSTANCE); } InboundDynamicTable() { this(Integer.MAX_VALUE); } public int getMaxSize() { return maxSize; } public void setMaxSize(final int maxSize) { this.maxSize = maxSize; evict(); } public int getCurrentSize() { return currentSize; } int staticLength() { return staticTable.length(); } int dynamicLength() { return headers.size(); } Header getDynamicEntry(final int index) { return headers.get(index); } public int length() { return staticTable.length() + headers.size(); } public HPackHeader getHeader(final int index) { final int length = length(); Args.check(index >= 1, "index %s cannot be less than 1", index); Args.check(index <= length, "length %s cannot be greater than index %s", length, index); return index <= staticTable.length() ? staticTable.get(index) : headers.get(index - staticTable.length() - 1); } public void add(final HPackHeader header) { final int entrySize = header.getTotalSize(); if (entrySize > this.maxSize) { clear(); return; } headers.addFirst(header); currentSize += entrySize; evict(); } private void clear() { currentSize = 0; headers.clear(); } private void evict() { while (currentSize > maxSize) { final HPackHeader header = headers.removeLast(); if (header != null) { currentSize -= header.getTotalSize(); } else { Asserts.check(currentSize == 0, "Current table size must be zero"); break; } } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/OutboundDynamicTable.java0100644 0000000 0000000 00000010624 00000250600 027133 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; final class OutboundDynamicTable { private final StaticTable staticTable; private final FifoLinkedList headers; private final Map> mapByName; private int maxSize; private int currentSize; OutboundDynamicTable(final int maxSize, final StaticTable staticTable) { this.staticTable = staticTable; this.headers = new FifoLinkedList(); this.mapByName = new HashMap<>(); this.maxSize = maxSize; this.currentSize = 0; } OutboundDynamicTable(final int maxSize) { this(maxSize, StaticTable.INSTANCE); } OutboundDynamicTable() { this(Integer.MAX_VALUE); } public int getMaxSize() { return maxSize; } public void setMaxSize(final int maxSize) { this.maxSize = maxSize; evict(); } public int getCurrentSize() { return currentSize; } int staticLength() { return staticTable.length(); } int dynamicLength() { return headers.size(); } Header getDynamicEntry(final int index) { return headers.get(index); } public int length() { return staticTable.length() + headers.size(); } public Header getHeader(final int index) { final int length = length(); Args.check(index >= 1, "index %s cannot be less then 1", index); Args.check(index <= length, "index %s cannot be greater then length %s ", index, length); return index <= staticTable.length() ? staticTable.get(index) : headers.get(index - staticTable.length() - 1); } public void add(final HPackHeader header) { final int entrySize = header.getTotalSize(); if (entrySize > this.maxSize) { clear(); this.mapByName.clear(); return; } final String key = header.getName(); final FifoLinkedList.InternalNode node = headers.addFirst(header); final LinkedList nodes = mapByName.computeIfAbsent(key, k -> new LinkedList<>()); nodes.addFirst(node); currentSize += entrySize; evict(); } private void clear() { currentSize = 0; headers.clear(); } public List getByName(final String key) { return this.mapByName.get(key); } private void evict() { while (currentSize > maxSize) { final FifoLinkedList.InternalNode node = headers.removeLast(); if (node != null) { final HPackHeader header = node.getHeader(); currentSize -= header.getTotalSize(); final String key = header.getName(); final LinkedList nodes = mapByName.get(key); if (nodes != null) { nodes.remove(node); } } else { Asserts.check(currentSize == 0, "Current table size must be zero"); break; } } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/StaticTable.java0100644 0000000 0000000 00000014400 00000250600 025252 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import org.apache.hc.core5.http2.H2PseudoRequestHeaders; import org.apache.hc.core5.http2.H2PseudoResponseHeaders; final class StaticTable { static final HPackHeader[] STANDARD_HEADERS = { new HPackHeader(H2PseudoRequestHeaders.AUTHORITY, ""), new HPackHeader(H2PseudoRequestHeaders.METHOD, "GET"), new HPackHeader(H2PseudoRequestHeaders.METHOD, "POST"), new HPackHeader(H2PseudoRequestHeaders.PATH, "/"), new HPackHeader(H2PseudoRequestHeaders.PATH, "/index.html"), new HPackHeader(H2PseudoRequestHeaders.SCHEME, "http"), new HPackHeader(H2PseudoRequestHeaders.SCHEME, "https"), new HPackHeader(H2PseudoResponseHeaders.STATUS, "200"), new HPackHeader(H2PseudoResponseHeaders.STATUS, "204"), new HPackHeader(H2PseudoResponseHeaders.STATUS, "206"), new HPackHeader(H2PseudoResponseHeaders.STATUS, "304"), new HPackHeader(H2PseudoResponseHeaders.STATUS, "400"), new HPackHeader(H2PseudoResponseHeaders.STATUS, "404"), new HPackHeader(H2PseudoResponseHeaders.STATUS, "500"), new HPackHeader("accept-charset", ""), new HPackHeader("accept-encoding", "gzip, deflate"), new HPackHeader("accept-language", ""), new HPackHeader("accept-ranges", ""), new HPackHeader("accept", ""), new HPackHeader("access-control-allow-origin", ""), new HPackHeader("age", ""), new HPackHeader("allow", ""), new HPackHeader("authorization", ""), new HPackHeader("cache-control", ""), new HPackHeader("content-disposition", ""), new HPackHeader("content-encoding", ""), new HPackHeader("content-language", ""), new HPackHeader("content-length", ""), new HPackHeader("content-location", ""), new HPackHeader("content-range", ""), new HPackHeader("content-type", ""), new HPackHeader("cookie", ""), new HPackHeader("date", ""), new HPackHeader("etag", ""), new HPackHeader("expect", ""), new HPackHeader("expires", ""), new HPackHeader("from", ""), new HPackHeader("host", ""), new HPackHeader("if-match", ""), new HPackHeader("if-modified-since", ""), new HPackHeader("if-none-match", ""), new HPackHeader("if-range", ""), new HPackHeader("if-unmodified-since", ""), new HPackHeader("last-modified", ""), new HPackHeader("link", ""), new HPackHeader("location", ""), new HPackHeader("max-forwards", ""), new HPackHeader("proxy-authenticate", ""), new HPackHeader("proxy-authorization", ""), new HPackHeader("range", ""), new HPackHeader("referer", ""), new HPackHeader("refresh", ""), new HPackHeader("retry-after", ""), new HPackHeader("server", ""), new HPackHeader("set-cookie", ""), new HPackHeader("strict-transport-security", ""), new HPackHeader("transfer-encoding", ""), new HPackHeader("user-agent", ""), new HPackHeader("vary", ""), new HPackHeader("via", ""), new HPackHeader("www-authenticate", "") }; final static StaticTable INSTANCE = new StaticTable(STANDARD_HEADERS); private final HPackHeader[] headers; private final ConcurrentMap> mapByName; StaticTable(final HPackHeader... headers) { this.headers = headers; this.mapByName = new ConcurrentHashMap<>(); for (int i = 0; i < headers.length; i++) { final HPackHeader header = headers[i]; final String key = header.getName(); final CopyOnWriteArrayList entries = this.mapByName.get(key); if (entries == null) { this.mapByName.put(key, new CopyOnWriteArrayList<>(new HPackEntry[] { new InternalEntry(header, i) })); } else { entries.add(new InternalEntry(header, i)); } } } public int length() { return this.headers.length; } public HPackHeader get(final int index) { return this.headers[index - 1]; } public List getByName(final String key) { return this.mapByName.get(key); } static class InternalEntry implements HPackEntry { private final HPackHeader header; private final int index; InternalEntry(final HPackHeader header, final int index) { this.header = header; this.index = index; } @Override public int getIndex() { return index + 1; } @Override public HPackHeader getHeader() { return header; } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/package-info.java0100644 0000000 0000000 00000002556 00000250600 025410 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * HTTP/2 HPACK APIs. *

* This Huffman codec implementation has been derived from Twitter HPack project * (https://github.com/twitter/hpack) *

*/ package org.apache.hc.core5.http2.hpack; httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/0040755 0000000 0000000 00000000000 00000250600 022067 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/BasicH2TransportMetrics.java0100644 0000000 0000000 00000003610 00000250600 027406 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl; import java.util.concurrent.atomic.AtomicLong; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http2.H2TransportMetrics; /** * Default implementation of {@link H2TransportMetrics}. * * @since 5.0 */ public class BasicH2TransportMetrics extends BasicHttpTransportMetrics implements H2TransportMetrics { private final AtomicLong framesTransferred; public BasicH2TransportMetrics() { this.framesTransferred = new AtomicLong(0); } @Override public long getFramesTransferred() { return framesTransferred.get(); } public void incrementFramesTransferred() { framesTransferred.incrementAndGet(); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/DefaultH2RequestConverter.java0100644 0000000 0000000 00000025142 00000250600 027752 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http2.H2MessageConverter; import org.apache.hc.core5.http2.H2PseudoRequestHeaders; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.util.TextUtils; /** * HTTP/2 request converter. * * @since 5.0 */ public final class DefaultH2RequestConverter implements H2MessageConverter { public final static DefaultH2RequestConverter INSTANCE = new DefaultH2RequestConverter(); @Override public HttpRequest convert(final List
headers) throws HttpException { String method = null; String scheme = null; String authority = null; String path = null; final List
messageHeaders = new ArrayList<>(); for (int i = 0; i < headers.size(); i++) { final Header header = headers.get(i); final String name = header.getName(); final String value = header.getValue(); if (name.startsWith(":")) { if (!FieldValidationSupport.isNameLowerCaseValid(name, 1, name.length())) { throw new ProtocolException("Header name '%s' is invalid", name); } if (!messageHeaders.isEmpty()) { throw new ProtocolException("Invalid sequence of headers (pseudo-headers must precede message headers)"); } switch (name) { case H2PseudoRequestHeaders.METHOD: if (method != null) { throw new ProtocolException("Multiple '%s' request headers are illegal", name); } method = value; break; case H2PseudoRequestHeaders.SCHEME: if (scheme != null) { throw new ProtocolException("Multiple '%s' request headers are illegal", name); } scheme = value; break; case H2PseudoRequestHeaders.PATH: if (path != null) { throw new ProtocolException("Multiple '%s' request headers are illegal", name); } path = value; break; case H2PseudoRequestHeaders.AUTHORITY: authority = value; break; default: throw new ProtocolException("Unsupported request header '%s'", name); } } else { if (!FieldValidationSupport.isNameLowerCaseValid(name)) { throw new ProtocolException("Header name '%s' is invalid", name); } messageHeaders.add(header); } if (!FieldValidationSupport.isValueValid(value)) { throw new ProtocolException("Header value is invalid"); } } if (method == null) { throw new ProtocolException("Mandatory request header '%s' not found", H2PseudoRequestHeaders.METHOD); } if (Method.CONNECT.isSame(method)) { if (authority == null) { throw new ProtocolException("Header '%s' is mandatory for CONNECT request", H2PseudoRequestHeaders.AUTHORITY); } if (scheme != null) { throw new ProtocolException("Header '%s' must not be set for CONNECT request", H2PseudoRequestHeaders.SCHEME); } if (path != null) { throw new ProtocolException("Header '%s' must not be set for CONNECT request", H2PseudoRequestHeaders.PATH); } } else { if (scheme == null) { throw new ProtocolException("Mandatory request header '%s' not found", H2PseudoRequestHeaders.SCHEME); } if (path == null) { throw new ProtocolException("Mandatory request header '%s' not found", H2PseudoRequestHeaders.PATH); } validatePathPseudoHeader(method, scheme, path); } final HttpRequest httpRequest = new BasicHttpRequest(method, path); httpRequest.setVersion(HttpVersion.HTTP_2); httpRequest.setScheme(scheme); try { httpRequest.setAuthority(URIAuthority.create(authority)); } catch (final URISyntaxException ex) { throw new ProtocolException(ex.getMessage(), ex); } httpRequest.setPath(path); for (int i = 0; i < messageHeaders.size(); i++) { httpRequest.addHeader(messageHeaders.get(i)); } return httpRequest; } @Override public List
convert(final HttpRequest message) throws HttpException { if (TextUtils.isBlank(message.getMethod())) { throw new ProtocolException("Request method is empty"); } final boolean optionMethod = Method.CONNECT.name().equalsIgnoreCase(message.getMethod()); if (optionMethod) { if (message.getAuthority() == null) { throw new ProtocolException("CONNECT request authority is not set"); } if (message.getPath() != null) { throw new ProtocolException("CONNECT request path must be null"); } } else { if (TextUtils.isBlank(message.getScheme())) { throw new ProtocolException("Request scheme is not set"); } if (TextUtils.isBlank(message.getPath())) { throw new ProtocolException("Request path is not set"); } } final List
headers = new ArrayList<>(); headers.add(new BasicHeader(H2PseudoRequestHeaders.METHOD, message.getMethod(), false)); if (optionMethod) { headers.add(new BasicHeader(H2PseudoRequestHeaders.AUTHORITY, message.getAuthority(), false)); } else { headers.add(new BasicHeader(H2PseudoRequestHeaders.SCHEME, message.getScheme(), false)); if (message.getAuthority() != null) { headers.add(new BasicHeader(H2PseudoRequestHeaders.AUTHORITY, message.getAuthority(), false)); } headers.add(new BasicHeader(H2PseudoRequestHeaders.PATH, message.getPath(), false)); } for (final Iterator
it = message.headerIterator(); it.hasNext(); ) { final Header header = it.next(); final String name = header.getName(); final String value = header.getValue(); if (!FieldValidationSupport.isNameValid(name)) { throw new ProtocolException("Header name '%s' is invalid", name); } if (!FieldValidationSupport.isValueValid(value)) { throw new ProtocolException("Header value is invalid"); } headers.add(new BasicHeader(TextUtils.toLowerCase(name), value)); } return headers; } /** * Validates the {@code :path} pseudo-header field based on the provided HTTP method and scheme. *

* This method performs the following validations: *

*
    *
  • Non-Empty Path: For 'http' or 'https' URIs, the {@code :path} pseudo-header field must not be empty.
  • *
  • OPTIONS Method: If the HTTP method is OPTIONS and the URI does not contain a path component, * the {@code :path} pseudo-header field must have a value of '*'.
  • *
  • Path Starting with '/': For 'http' or 'https' URIs, the {@code :path} pseudo-header field must either start with '/' or be '*'.
  • *
* * @param method The HTTP method of the request, e.g., GET, POST, OPTIONS, etc. * @param scheme The scheme of the request, e.g., http or https. * @param path The value of the {@code :path} pseudo-header field. * @throws ProtocolException if any of the validations fail. */ private void validatePathPseudoHeader(final String method, final String scheme, final String path) throws ProtocolException { if (URIScheme.HTTP.name().equalsIgnoreCase(scheme) || URIScheme.HTTPS.name().equalsIgnoreCase(scheme)) { if (TextUtils.isBlank(path)) { throw new ProtocolException("':path' pseudo-header field must not be empty for 'http' or 'https' URIs"); } else { final boolean isRoot = path.startsWith("/"); if (Method.OPTIONS.isSame(method)) { if (!"*".equals(path) && !isRoot) { throw new ProtocolException("OPTIONS request for an 'http' or 'https' URI must have a ':path' pseudo-header field with a value of '*' or '/'"); } } else { if (!isRoot) { throw new ProtocolException("':path' pseudo-header field for 'http' or 'https' URIs must start with '/'"); } } } } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/DefaultH2ResponseConverter.java0100644 0000000 0000000 00000014127 00000250600 030121 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Locale; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http2.H2MessageConverter; import org.apache.hc.core5.http2.H2PseudoResponseHeaders; import org.apache.hc.core5.util.TextUtils; /** * HTTP/2 response converter. * * @since 5.0 */ public class DefaultH2ResponseConverter implements H2MessageConverter { public final static DefaultH2ResponseConverter INSTANCE = new DefaultH2ResponseConverter(); @Override public HttpResponse convert(final List
headers) throws HttpException { String statusText = null; final List
messageHeaders = new ArrayList<>(); int cookieCount = 0; for (int i = 0; i < headers.size(); i++) { final Header header = headers.get(i); final String name = header.getName(); final String value = header.getValue(); if (name.startsWith(":")) { if (!FieldValidationSupport.isNameLowerCaseValid(name, 1, name.length())) { throw new ProtocolException("Header name '%s' is invalid", name); } if (!messageHeaders.isEmpty()) { throw new ProtocolException("Invalid sequence of headers (pseudo-headers must precede message headers)"); } if (name.equals(H2PseudoResponseHeaders.STATUS)) { if (statusText != null) { throw new ProtocolException("Multiple '%s' response headers are illegal", name); } statusText = value; } else { throw new ProtocolException("Unsupported response header '%s'", name); } } else { if (!FieldValidationSupport.isNameLowerCaseValid(name)) { throw new ProtocolException("Header name '%s' is invalid", name); } if (name.equalsIgnoreCase(HttpHeaders.COOKIE)) { cookieCount++; } messageHeaders.add(header); } if (!FieldValidationSupport.isValueValid(value)) { throw new ProtocolException("Header value is invalid"); } } if (statusText == null) { throw new ProtocolException("Mandatory response header '%s' not found", H2PseudoResponseHeaders.STATUS); } final int statusCode; try { statusCode = Integer.parseInt(statusText); } catch (final NumberFormatException ex) { throw new ProtocolException("Invalid response status: " + statusText); } final HttpResponse response = new BasicHttpResponse(statusCode, null); response.setVersion(HttpVersion.HTTP_2); for (int i = 0; i < messageHeaders.size(); i++) { response.addHeader(messageHeaders.get(i)); } if (cookieCount > 1) { final StringBuilder buf = new StringBuilder(); for (final Iterator
it = response.headerIterator(HttpHeaders.COOKIE); it.hasNext(); ) { if (buf.length() > 0) { buf.append("; "); } final Header cookie = it.next(); buf.append(cookie.getValue()); it.remove(); } response.setHeader(HttpHeaders.COOKIE.toLowerCase(Locale.ROOT), buf.toString()); } return response; } @Override public List
convert(final HttpResponse message) throws HttpException { final int code = message.getCode(); if (code < 100 || code >= 600) { throw new ProtocolException("Response status %s is invalid", code); } final List
headers = new ArrayList<>(); headers.add(new BasicHeader(H2PseudoResponseHeaders.STATUS, Integer.toString(code), false)); for (final Iterator
it = message.headerIterator(); it.hasNext(); ) { final Header header = it.next(); final String name = header.getName(); final String value = header.getValue(); if (!FieldValidationSupport.isNameValid(name)) { throw new ProtocolException("Header name '%s' is invalid", name); } if (!FieldValidationSupport.isValueValid(value)) { throw new ProtocolException("Header value is invalid"); } headers.add(new BasicHeader(TextUtils.toLowerCase(name), value)); } return headers; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/FieldValidationSupport.java0100644 0000000 0000000 00000005730 00000250600 027367 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl; import org.apache.hc.core5.annotation.Internal; @Internal public class FieldValidationSupport { public static boolean isWhitespace(final char ch) { return ch == 0x20 || ch == 0x09; } public static boolean isNameCharValid(final char ch) { return ch > 0x20 && ch != ':' && ch < 0x7f; } public static boolean isNameCharLowerCaseValid(final char ch) { return isNameCharValid(ch) && (ch < 0x41 || ch > 0x5a); } public static boolean isValueCharValid(final char ch) { return ch != 0x00 && ch != 0x0a && ch != 0x0d; } public static boolean isNameValid(final CharSequence s, final int pos, final int len) { for (int i = pos; i < len; i++) { if (!isNameCharValid(s.charAt(i))) { return false; } } return true; } public static boolean isNameValid(final CharSequence s) { return isNameValid(s, 0, s.length()); } public static boolean isNameLowerCaseValid(final CharSequence s, final int pos, final int len) { for (int i = pos; i < len; i++) { if (!isNameCharLowerCaseValid(s.charAt(i))) { return false; } } return true; } public static boolean isNameLowerCaseValid(final CharSequence s) { return isNameLowerCaseValid(s, 0, s.length()); } public static boolean isValueValid(final CharSequence s) { if (s.length() == 0) { return true; } if (isWhitespace(s.charAt(0)) || isWhitespace(s.charAt(s.length() - 1))) { return false; } for (int i = 0; i < s.length(); i++) { if (!isValueCharValid(s.charAt(i))) { return false; } } return true; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/H2Processors.java0100644 0000000 0000000 00000010516 00000250600 025266 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl; import org.apache.hc.core5.http.impl.HttpProcessors; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http.protocol.HttpProcessorBuilder; import org.apache.hc.core5.http.protocol.RequestConformance; import org.apache.hc.core5.http.protocol.RequestExpectContinue; import org.apache.hc.core5.http.protocol.RequestUserAgent; import org.apache.hc.core5.http.protocol.ResponseConformance; import org.apache.hc.core5.http.protocol.ResponseDate; import org.apache.hc.core5.http.protocol.ResponseServer; import org.apache.hc.core5.http2.protocol.H2RequestConformance; import org.apache.hc.core5.http2.protocol.H2RequestConnControl; import org.apache.hc.core5.http2.protocol.H2RequestContent; import org.apache.hc.core5.http2.protocol.H2RequestTargetHost; import org.apache.hc.core5.http2.protocol.H2RequestValidateHost; import org.apache.hc.core5.http2.protocol.H2ResponseConformance; import org.apache.hc.core5.http2.protocol.H2ResponseConnControl; import org.apache.hc.core5.http2.protocol.H2ResponseContent; import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.util.VersionInfo; /** * @since 5.0 */ public final class H2Processors { private final static String SOFTWARE = "Apache-HttpCore"; public static HttpProcessorBuilder customServer(final String serverInfo) { return HttpProcessorBuilder.create() .addAll( ResponseConformance.INSTANCE, H2ResponseConformance.INSTANCE, ResponseDate.INSTANCE, new ResponseServer(!TextUtils.isBlank(serverInfo) ? serverInfo : VersionInfo.getSoftwareInfo(SOFTWARE, "org.apache.hc.core5", H2Processors.class)), H2ResponseContent.INSTANCE, H2ResponseConnControl.INSTANCE) .addAll( H2RequestValidateHost.INSTANCE, H2RequestConformance.INSTANCE, RequestConformance.INSTANCE); } public static HttpProcessor server(final String serverInfo) { return customServer(serverInfo).build(); } public static HttpProcessor server() { return customServer(null).build(); } public static HttpProcessorBuilder customClient(final String agentInfo) { return HttpProcessorBuilder.create() .addAll( H2ResponseConformance.INSTANCE) .addAll( H2RequestConformance.INSTANCE, H2RequestTargetHost.INSTANCE, H2RequestContent.INSTANCE, H2RequestConnControl.INSTANCE, new RequestUserAgent(!TextUtils.isBlank(agentInfo) ? agentInfo : VersionInfo.getSoftwareInfo(SOFTWARE, "org.apache.hc.core5", HttpProcessors.class)), RequestExpectContinue.INSTANCE); } public static HttpProcessor client(final String agentInfo) { return customClient(agentInfo).build(); } public static HttpProcessor client() { return customClient(null).build(); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/package-info.java0100644 0000000 0000000 00000002401 00000250600 025250 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Default implementations of core HTTP/2 APIs. */ package org.apache.hc.core5.http2.impl; httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/io/0040755 0000000 0000000 00000000000 00000250600 022476 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/io/FrameInputBuffer.java0100644 0000000 0000000 00000012303 00000250600 026541 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.io; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http2.H2ConnectionException; import org.apache.hc.core5.http2.H2CorruptFrameException; import org.apache.hc.core5.http2.H2Error; import org.apache.hc.core5.http2.H2TransportMetrics; import org.apache.hc.core5.http2.frame.FrameConsts; import org.apache.hc.core5.http2.frame.FrameFlag; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.impl.BasicH2TransportMetrics; import org.apache.hc.core5.util.Args; /** * Frame input buffer for HTTP/2 blocking connections. * * @since 5.0 */ public final class FrameInputBuffer { private final BasicH2TransportMetrics metrics; private final int maxFramePayloadSize; private final byte[] buffer; private int off; private int dataLen; FrameInputBuffer(final BasicH2TransportMetrics metrics, final int bufferLen, final int maxFramePayloadSize) { Args.notNull(metrics, "HTTP2 transport metrics"); Args.positive(maxFramePayloadSize, "Maximum payload size"); this.metrics = metrics; this.maxFramePayloadSize = maxFramePayloadSize; this.buffer = new byte[bufferLen]; this.dataLen = 0; } public FrameInputBuffer(final BasicH2TransportMetrics metrics, final int maxFramePayloadSize) { this(metrics, FrameConsts.HEAD_LEN + maxFramePayloadSize, maxFramePayloadSize); } public FrameInputBuffer(final int maxFramePayloadSize) { this(new BasicH2TransportMetrics(), maxFramePayloadSize); } boolean hasData() { return this.dataLen > 0; } void fillBuffer(final InputStream inStream, final int requiredLen) throws IOException { while (dataLen < requiredLen) { if (off > 0) { System.arraycopy(buffer, off, buffer, 0, dataLen); off = 0; } final int bytesRead = inStream.read(buffer, off + dataLen, buffer.length - dataLen); if (bytesRead == -1) { if (dataLen > 0) { throw new H2CorruptFrameException("Corrupt or incomplete HTTP2 frame"); } throw new ConnectionClosedException(); } dataLen += bytesRead; this.metrics.incrementBytesTransferred(bytesRead); } } public RawFrame read(final InputStream inStream) throws IOException { fillBuffer(inStream, FrameConsts.HEAD_LEN); final int payloadOff = FrameConsts.HEAD_LEN; final int payloadLen = (buffer[off] & 0xff) << 16 | (buffer[off + 1] & 0xff) << 8 | (buffer[off + 2] & 0xff); final int type = buffer[off + 3] & 0xff; final int flags = buffer[off + 4] & 0xff; final int streamId = Math.abs(buffer[off + 5] & 0xff) << 24 | (buffer[off + 6] & 0xff << 16) | (buffer[off + 7] & 0xff) << 8 | (buffer[off + 8] & 0xff); if (payloadLen > maxFramePayloadSize) { throw new H2ConnectionException(H2Error.FRAME_SIZE_ERROR, "Frame size exceeds maximum"); } final int frameLen = payloadOff + payloadLen; fillBuffer(inStream, frameLen); if ((flags & FrameFlag.PADDED.getValue()) > 0) { if (payloadLen == 0) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Inconsistent padding"); } final int padding = buffer[off + FrameConsts.HEAD_LEN] & 0xff; if (payloadLen < padding + 1) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Inconsistent padding"); } } final ByteBuffer payload = payloadLen > 0 ? ByteBuffer.wrap(buffer, off + payloadOff, payloadLen) : null; final RawFrame frame = new RawFrame(type, flags, streamId, payload); off += frameLen; dataLen -= frameLen; this.metrics.incrementFramesTransferred(); return frame; } public H2TransportMetrics getMetrics() { return metrics; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/io/FrameOutputBuffer.java0100644 0000000 0000000 00000010311 00000250600 026737 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.io; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import org.apache.hc.core5.http2.H2ConnectionException; import org.apache.hc.core5.http2.H2Error; import org.apache.hc.core5.http2.H2TransportMetrics; import org.apache.hc.core5.http2.frame.FrameConsts; import org.apache.hc.core5.http2.frame.FrameFlag; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.impl.BasicH2TransportMetrics; import org.apache.hc.core5.util.Args; /** * Frame output buffer for HTTP/2 blocking connections. * * @since 5.0 */ public final class FrameOutputBuffer { private final BasicH2TransportMetrics metrics; private final int maxFramePayloadSize; private final byte[] buffer; public FrameOutputBuffer(final BasicH2TransportMetrics metrics, final int maxFramePayloadSize) { super(); Args.notNull(metrics, "HTTP2 transport metrics"); Args.positive(maxFramePayloadSize, "Maximum payload size"); this.metrics = metrics; this.maxFramePayloadSize = maxFramePayloadSize; this.buffer = new byte[FrameConsts.HEAD_LEN + maxFramePayloadSize + FrameConsts.MAX_PADDING + 1]; } public FrameOutputBuffer(final int maxFramePayloadSize) { this(new BasicH2TransportMetrics(), maxFramePayloadSize); } public void write(final RawFrame frame, final OutputStream outStream) throws IOException { if (frame == null) { return; } final int type = frame.getType(); final long streamId = frame.getStreamId(); final int flags = frame.getFlags(); final ByteBuffer payload = frame.getPayload(); final int payloadLen = payload != null ? payload.remaining() : 0; if (payload != null && payload.remaining() > maxFramePayloadSize) { throw new H2ConnectionException(H2Error.FRAME_SIZE_ERROR, "Frame size exceeds maximum"); } buffer[0] = (byte) (payloadLen >> 16 & 0xff); buffer[1] = (byte) (payloadLen >> 8 & 0xff); buffer[2] = (byte) (payloadLen & 0xff); buffer[3] = (byte) (type & 0xff); buffer[4] = (byte) (flags & 0xff); buffer[5] = (byte) (streamId >> 24 & 0xff); buffer[6] = (byte) (streamId >> 16 & 0xff); buffer[7] = (byte) (streamId >> 8 & 0xff); buffer[8] = (byte) (streamId & 0xff); int frameLen = FrameConsts.HEAD_LEN; int padding = 0; if ((flags & FrameFlag.PADDED.getValue()) > 0) { padding = 16; buffer[9] = (byte) (padding & 0xff); frameLen++; } if (payload != null) { payload.get(buffer, frameLen, payload.remaining()); frameLen += payloadLen; } for (int i = 0; i < padding; i++) { buffer[frameLen++] = 0; } outStream.write(buffer, 0, frameLen); metrics.incrementFramesTransferred(); metrics.incrementBytesTransferred(frameLen); } public H2TransportMetrics getMetrics() { return metrics; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/io/FrameOutputStream.java0100644 0000000 0000000 00000006440 00000250600 026771 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.io; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import org.apache.hc.core5.util.Args; /** * @since 5.0 */ abstract class FrameOutputStream extends OutputStream { private final OutputStream outputStream; private final byte[] cache; private int cachePosition; public FrameOutputStream(final int minChunkSize, final OutputStream outputStream) { super(); this.outputStream = Args.notNull(outputStream, "Output stream"); this.cache = new byte[minChunkSize]; } protected abstract void write(final ByteBuffer src, final boolean endStream, final OutputStream outputStream) throws IOException; private void flushCache(final boolean endStream) throws IOException { if (this.cachePosition > 0) { write(ByteBuffer.wrap(this.cache, 0, this.cachePosition), endStream, this.outputStream); this.cachePosition = 0; } } @Override public void write(final int b) throws IOException { this.cache[this.cachePosition] = (byte) b; this.cachePosition++; if (this.cachePosition == this.cache.length) { flushCache(false); } } @Override public void write(final byte[] b) throws IOException { write(b, 0, b.length); } @Override public void write(final byte[] src, final int off, final int len) throws IOException { if (len >= this.cache.length - this.cachePosition) { flushCache(false); write(ByteBuffer.wrap(src, off, len), false, this.outputStream); } else { System.arraycopy(src, off, cache, this.cachePosition, len); this.cachePosition += len; } } @Override public void flush() throws IOException { flushCache(false); this.outputStream.flush(); } @Override public void close() throws IOException { if (this.cachePosition > 0) { flushCache(true); } else { write(null, true, this.outputStream); } flushCache(true); this.outputStream.flush(); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/0040755 0000000 0000000 00000000000 00000250600 022654 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2IOEventHandler.java0100644 0000000 0000000 00000010645 00000250600 030207 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; import javax.net.ssl.SSLSession; import org.apache.hc.core5.http.EndpointDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.impl.nio.HttpConnectionEventHandler; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; class AbstractH2IOEventHandler implements HttpConnectionEventHandler { final AbstractH2StreamMultiplexer streamMultiplexer; AbstractH2IOEventHandler(final AbstractH2StreamMultiplexer streamMultiplexer) { this.streamMultiplexer = Args.notNull(streamMultiplexer, "Stream multiplexer"); } @Override public void connected(final IOSession session) throws IOException { try { streamMultiplexer.onConnect(); } catch (final HttpException ex) { streamMultiplexer.onException(ex); } } @Override public void inputReady(final IOSession session, final ByteBuffer src) throws IOException { try { streamMultiplexer.onInput(src); } catch (final HttpException ex) { streamMultiplexer.onException(ex); } } @Override public void outputReady(final IOSession session) throws IOException { try { streamMultiplexer.onOutput(); } catch (final HttpException ex) { streamMultiplexer.onException(ex); } } @Override public void timeout(final IOSession session, final Timeout timeout) throws IOException { try { streamMultiplexer.onTimeout(timeout); } catch (final HttpException ex) { streamMultiplexer.onException(ex); } } @Override public void exception(final IOSession session, final Exception cause) { streamMultiplexer.onException(cause); } @Override public void disconnected(final IOSession session) { streamMultiplexer.onDisconnect(); } @Override public void close() throws IOException { streamMultiplexer.close(); } @Override public void close(final CloseMode closeMode) { streamMultiplexer.close(closeMode); } @Override public boolean isOpen() { return streamMultiplexer.isOpen(); } @Override public void setSocketTimeout(final Timeout timeout) { streamMultiplexer.setSocketTimeout(timeout); } @Override public SSLSession getSSLSession() { return streamMultiplexer.getSSLSession(); } @Override public EndpointDetails getEndpointDetails() { return streamMultiplexer.getEndpointDetails(); } @Override public Timeout getSocketTimeout() { return streamMultiplexer.getSocketTimeout(); } @Override public ProtocolVersion getProtocolVersion() { return streamMultiplexer.getProtocolVersion(); } @Override public SocketAddress getRemoteAddress() { return streamMultiplexer.getRemoteAddress(); } @Override public SocketAddress getLocalAddress() { return streamMultiplexer.getLocalAddress(); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java0100644 0000000 0000000 00000205630 00000250600 031066 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.net.SocketAddress; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.charset.StandardCharsets; import java.util.Deque; import java.util.Iterator; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; import org.apache.hc.core5.concurrent.CancellableDependency; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.EndpointDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpStreamResetException; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.RequestNotExecutedException; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.impl.BasicEndpointDetails; import org.apache.hc.core5.http.impl.BasicHttpConnectionMetrics; import org.apache.hc.core5.http.impl.CharCodingSupport; import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.AsyncPushProducer; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.nio.command.CommandSupport; import org.apache.hc.core5.http.nio.command.RequestExecutionCommand; import org.apache.hc.core5.http.nio.command.ShutdownCommand; import org.apache.hc.core5.http.nio.command.StaleCheckCommand; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http2.H2ConnectionException; import org.apache.hc.core5.http2.H2Error; import org.apache.hc.core5.http2.H2StreamResetException; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.config.H2Param; import org.apache.hc.core5.http2.config.H2Setting; import org.apache.hc.core5.http2.frame.FrameFactory; import org.apache.hc.core5.http2.frame.FrameFlag; import org.apache.hc.core5.http2.frame.FrameType; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.frame.StreamIdGenerator; import org.apache.hc.core5.http2.hpack.HPackDecoder; import org.apache.hc.core5.http2.hpack.HPackEncoder; import org.apache.hc.core5.http2.impl.BasicH2TransportMetrics; import org.apache.hc.core5.http2.nio.AsyncPingHandler; import org.apache.hc.core5.http2.nio.command.PingCommand; import org.apache.hc.core5.http2.nio.command.PushResponseCommand; import org.apache.hc.core5.http2.priority.PriorityParamsParser; import org.apache.hc.core5.http2.priority.PriorityValue; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.Command; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.reactor.ssl.TlsDetails; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.ByteArrayBuffer; import org.apache.hc.core5.util.Identifiable; import org.apache.hc.core5.util.Timeout; abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnection { private static final long CONNECTION_WINDOW_LOW_MARK = 10 * 1024 * 1024; enum ConnectionHandshake { READY, ACTIVE, GRACEFUL_SHUTDOWN, SHUTDOWN } enum SettingsHandshake { READY, TRANSMITTED, ACKED } private final ProtocolIOSession ioSession; private final FrameFactory frameFactory; private final HttpProcessor httpProcessor; private final H2Config localConfig; private final BasicH2TransportMetrics inputMetrics; private final BasicH2TransportMetrics outputMetrics; private final BasicHttpConnectionMetrics connMetrics; private final FrameInputBuffer inputBuffer; private final FrameOutputBuffer outputBuffer; private final Deque outputQueue; private final HPackEncoder hPackEncoder; private final HPackDecoder hPackDecoder; private final H2Streams streams; private final Queue pingHandlers; private final AtomicInteger connInputWindow; private final AtomicInteger connOutputWindow; private final AtomicInteger outputRequests; private final H2StreamListener streamListener; private ConnectionHandshake connState = ConnectionHandshake.READY; private SettingsHandshake localSettingState = SettingsHandshake.READY; private SettingsHandshake remoteSettingState = SettingsHandshake.READY; private int initInputWinSize; private int initOutputWinSize; private int lowMark; private volatile H2Config remoteConfig; private Continuation continuation; private EndpointDetails endpointDetails; private boolean goAwayReceived; private volatile boolean peerNoRfc7540Priorities; AbstractH2StreamMultiplexer( final ProtocolIOSession ioSession, final FrameFactory frameFactory, final StreamIdGenerator idGenerator, final HttpProcessor httpProcessor, final CharCodingConfig charCodingConfig, final H2Config h2Config, final H2StreamListener streamListener) { this.ioSession = Args.notNull(ioSession, "IO session"); this.frameFactory = Args.notNull(frameFactory, "Frame factory"); this.httpProcessor = Args.notNull(httpProcessor, "HTTP processor"); this.streams = new H2Streams(idGenerator); this.localConfig = h2Config != null ? h2Config : H2Config.DEFAULT; this.inputMetrics = new BasicH2TransportMetrics(); this.outputMetrics = new BasicH2TransportMetrics(); this.connMetrics = new BasicHttpConnectionMetrics(this.inputMetrics, this.outputMetrics); this.inputBuffer = new FrameInputBuffer(this.inputMetrics, this.localConfig.getMaxFrameSize()); this.outputBuffer = new FrameOutputBuffer(this.outputMetrics, this.localConfig.getMaxFrameSize()); this.outputQueue = new ConcurrentLinkedDeque<>(); this.pingHandlers = new ConcurrentLinkedQueue<>(); this.outputRequests = new AtomicInteger(0); this.hPackEncoder = new HPackEncoder(H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(charCodingConfig)); this.hPackDecoder = new HPackDecoder(H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createDecoder(charCodingConfig)); this.remoteConfig = H2Config.INIT; this.connInputWindow = new AtomicInteger(H2Config.INIT.getInitialWindowSize()); this.connOutputWindow = new AtomicInteger(H2Config.INIT.getInitialWindowSize()); this.initInputWinSize = H2Config.INIT.getInitialWindowSize(); this.initOutputWinSize = H2Config.INIT.getInitialWindowSize(); this.hPackDecoder.setMaxListSize(this.localConfig.getMaxHeaderListSize()); this.lowMark = H2Config.INIT.getInitialWindowSize() / 2; this.streamListener = streamListener; } @Override public String getId() { return ioSession.getId(); } BasicHttpConnectionMetrics getConnMetrics() { return connMetrics; } HttpProcessor getHttpProcessor() { return httpProcessor; } void submitCommand(final Command command) { ioSession.enqueue(command, Command.Priority.NORMAL); } abstract void validateSetting(H2Param param, int value) throws H2ConnectionException; abstract H2Setting[] generateSettings(H2Config localConfig); abstract void acceptHeaderFrame() throws H2ConnectionException; abstract void acceptPushRequest() throws H2ConnectionException; abstract void acceptPushFrame() throws H2ConnectionException; abstract H2StreamHandler incomingRequest(H2StreamChannel channel) throws IOException; abstract H2StreamHandler incomingPushPromise(H2StreamChannel channel, HandlerFactory pushHandlerFactory) throws IOException; abstract H2StreamHandler outgoingRequest(H2StreamChannel channel, AsyncClientExchangeHandler exchangeHandler, HandlerFactory pushHandlerFactory, HttpContext context) throws IOException; abstract H2StreamHandler outgoingPushPromise(H2StreamChannel channel, AsyncPushProducer pushProducer) throws IOException; abstract boolean allowGracefulAbort(H2Stream stream); private int updateWindow(final AtomicInteger window, final int delta) throws ArithmeticException { for (;;) { final int current = window.get(); final long newValue = (long) current + delta; if (Math.abs(newValue) > 0x7fffffffL) { throw new ArithmeticException("Update causes flow control window to exceed " + Integer.MAX_VALUE); } if (window.compareAndSet(current, (int) newValue)) { return (int) newValue; } } } private int updateWindowMax(final AtomicInteger window) throws ArithmeticException { for (;;) { final int current = window.get(); if (window.compareAndSet(current, Integer.MAX_VALUE)) { return Integer.MAX_VALUE - current; } } } private int updateInputWindow( final int streamId, final AtomicInteger window, final int delta) throws ArithmeticException { final int newSize = updateWindow(window, delta); if (streamListener != null) { streamListener.onInputFlowControl(this, streamId, delta, newSize); } return newSize; } private int updateOutputWindow( final int streamId, final AtomicInteger window, final int delta) throws ArithmeticException { final int newSize = updateWindow(window, delta); if (streamListener != null) { streamListener.onOutputFlowControl(this, streamId, delta, newSize); } return newSize; } private void commitFrameInternal(final RawFrame frame) throws IOException { if (outputBuffer.isEmpty() && outputQueue.isEmpty()) { if (streamListener != null) { streamListener.onFrameOutput(this, frame.getStreamId(), frame); } outputBuffer.write(frame, ioSession); } else { outputQueue.addLast(frame); } ioSession.setEvent(SelectionKey.OP_WRITE); } private void commitFrame(final RawFrame frame) throws IOException { Args.notNull(frame, "Frame"); ioSession.getLock().lock(); try { commitFrameInternal(frame); } finally { ioSession.getLock().unlock(); } } private void commitHeaders( final int streamId, final List headers, final boolean endStream) throws IOException { if (streamListener != null) { streamListener.onHeaderOutput(this, streamId, headers); } final ByteArrayBuffer buf = new ByteArrayBuffer(512); hPackEncoder.encodeHeaders(buf, headers, localConfig.isCompressionEnabled()); int off = 0; int remaining = buf.length(); boolean continuation = false; while (remaining > 0) { final int chunk = Math.min(remoteConfig.getMaxFrameSize(), remaining); final ByteBuffer payload = ByteBuffer.wrap(buf.array(), off, chunk); remaining -= chunk; off += chunk; final boolean endHeaders = remaining == 0; final RawFrame frame; if (!continuation) { frame = frameFactory.createHeaders(streamId, payload, endHeaders, endStream); continuation = true; } else { frame = frameFactory.createContinuation(streamId, payload, endHeaders); } commitFrameInternal(frame); } } private void commitPushPromise( final int streamId, final int promisedStreamId, final List
headers) throws IOException { if (headers == null || headers.isEmpty()) { throw new H2ConnectionException(H2Error.INTERNAL_ERROR, "Message headers are missing"); } if (streamListener != null) { streamListener.onHeaderOutput(this, streamId, headers); } final ByteArrayBuffer buf = new ByteArrayBuffer(512); buf.append((byte)(promisedStreamId >> 24)); buf.append((byte)(promisedStreamId >> 16)); buf.append((byte)(promisedStreamId >> 8)); buf.append((byte) promisedStreamId); hPackEncoder.encodeHeaders(buf, headers, localConfig.isCompressionEnabled()); int off = 0; int remaining = buf.length(); boolean continuation = false; while (remaining > 0) { final int chunk = Math.min(remoteConfig.getMaxFrameSize(), remaining); final ByteBuffer payload = ByteBuffer.wrap(buf.array(), off, chunk); remaining -= chunk; off += chunk; final boolean endHeaders = remaining == 0; final RawFrame frame; if (!continuation) { frame = frameFactory.createPushPromise(streamId, payload, endHeaders); continuation = true; } else { frame = frameFactory.createContinuation(streamId, payload, endHeaders); } commitFrameInternal(frame); } } private void streamDataFrame( final int streamId, final AtomicInteger streamOutputWindow, final ByteBuffer payload, final int chunk) throws IOException { final RawFrame dataFrame = frameFactory.createData(streamId, payload, false); if (streamListener != null) { streamListener.onFrameOutput(this, streamId, dataFrame); } updateOutputWindow(0, connOutputWindow, -chunk); updateOutputWindow(streamId, streamOutputWindow, -chunk); outputBuffer.write(dataFrame, ioSession); } private int streamData( final int streamId, final AtomicInteger streamOutputWindow, final ByteBuffer payload) throws IOException { if (outputBuffer.isEmpty() && outputQueue.isEmpty()) { final int capacity = Math.min(connOutputWindow.get(), streamOutputWindow.get()); if (capacity <= 0) { return 0; } final int maxPayloadSize = Math.min(capacity, outputBuffer.getMaxFramePayloadSize()); final int chunk; if (payload.remaining() <= maxPayloadSize) { chunk = payload.remaining(); streamDataFrame(streamId, streamOutputWindow, payload, chunk); } else { chunk = maxPayloadSize; final int originalLimit = payload.limit(); try { payload.limit(payload.position() + chunk); streamDataFrame(streamId, streamOutputWindow, payload, chunk); } finally { payload.limit(originalLimit); } } payload.position(payload.position() + chunk); ioSession.setEvent(SelectionKey.OP_WRITE); return chunk; } return 0; } private void incrementInputCapacity( final int streamId, final AtomicInteger inputWindow, final int inputCapacity) throws IOException { if (inputCapacity > 0) { final int streamWinSize = inputWindow.get(); final int remainingCapacity = Integer.MAX_VALUE - streamWinSize; final int chunk = Math.min(inputCapacity, remainingCapacity); if (chunk != 0) { updateInputWindow(streamId, inputWindow, chunk); final RawFrame windowUpdateFrame = frameFactory.createWindowUpdate(streamId, chunk); commitFrame(windowUpdateFrame); } } } void requestSessionOutput() { outputRequests.incrementAndGet(); ioSession.setEvent(SelectionKey.OP_WRITE); } public final void onConnect() throws HttpException, IOException { connState = ConnectionHandshake.ACTIVE; final RawFrame settingsFrame = frameFactory.createSettings(generateSettings(localConfig)); commitFrame(settingsFrame); localSettingState = SettingsHandshake.TRANSMITTED; maximizeWindow(0, connInputWindow); if (streamListener != null) { final int initInputWindow = connInputWindow.get(); streamListener.onInputFlowControl(this, 0, initInputWindow, initInputWindow); final int initOutputWindow = connOutputWindow.get(); streamListener.onOutputFlowControl(this, 0, initOutputWindow, initOutputWindow); } } public final void onInput(final ByteBuffer src) throws HttpException, IOException { if (connState == ConnectionHandshake.SHUTDOWN) { ioSession.clearEvent(SelectionKey.OP_READ); } else { for (;;) { final RawFrame frame = inputBuffer.read(src, ioSession); if (frame != null) { if (streamListener != null) { streamListener.onFrameInput(this, frame.getStreamId(), frame); } consumeFrame(frame); } else { if (inputBuffer.isEndOfStream()) { if (connState == ConnectionHandshake.ACTIVE) { final RawFrame goAway = frameFactory.createGoAway(streams.getLastRemoteId(), H2Error.NO_ERROR, "Unexpected end of stream"); commitFrame(goAway); } connState = ConnectionHandshake.SHUTDOWN; requestSessionOutput(); } break; } } } } public final void onOutput() throws HttpException, IOException { ioSession.getLock().lock(); try { if (!outputBuffer.isEmpty()) { outputBuffer.flush(ioSession); } while (outputBuffer.isEmpty()) { final RawFrame frame = outputQueue.poll(); if (frame != null) { if (streamListener != null) { streamListener.onFrameOutput(this, frame.getStreamId(), frame); } outputBuffer.write(frame, ioSession); } else { break; } } } finally { ioSession.getLock().unlock(); } if (connState.compareTo(ConnectionHandshake.SHUTDOWN) < 0) { if (connOutputWindow.get() > 0 && remoteSettingState == SettingsHandshake.ACKED) { produceOutput(); } final int pendingOutputRequests = outputRequests.get(); boolean outputPending = false; if (!streams.isEmpty() && connOutputWindow.get() > 0) { for (final Iterator it = streams.iterator(); it.hasNext(); ) { final H2Stream stream = it.next(); if (!stream.isLocalClosed() && stream.getOutputWindow().get() > 0 && stream.isOutputReady()) { outputPending = true; break; } } } ioSession.getLock().lock(); try { if (!outputPending && outputBuffer.isEmpty() && outputQueue.isEmpty() && outputRequests.compareAndSet(pendingOutputRequests, 0)) { ioSession.clearEvent(SelectionKey.OP_WRITE); } else { outputRequests.addAndGet(-pendingOutputRequests); } } finally { ioSession.getLock().unlock(); } } if (connState.compareTo(ConnectionHandshake.ACTIVE) <= 0 && remoteSettingState == SettingsHandshake.ACKED) { while (streams.getLocalCount() < remoteConfig.getMaxConcurrentStreams()) { final Command command = ioSession.poll(); if (command == null) { break; } if (command instanceof ShutdownCommand) { executeShutdown((ShutdownCommand) command); } else if (command instanceof PingCommand) { executePing((PingCommand) command); } else if (command instanceof RequestExecutionCommand) { executeRequest((RequestExecutionCommand) command); } else if (command instanceof PushResponseCommand) { executePush((PushResponseCommand) command); } else if (command instanceof StaleCheckCommand) { executeStaleCheck((StaleCheckCommand) command); } if (!outputQueue.isEmpty()) { return; } } } if (connState.compareTo(ConnectionHandshake.GRACEFUL_SHUTDOWN) == 0) { int liveStreams = 0; for (final Iterator it = streams.iterator(); it.hasNext(); ) { final H2Stream stream = it.next(); if (stream.isClosedPastLingerDeadline()) { streams.dropStreamId(stream.getId()); it.remove(); } else { if (streams.isSameSide(stream.getId()) || stream.getId() <= streams.getLastRemoteId()) { liveStreams++; } } } if (liveStreams == 0) { connState = ConnectionHandshake.SHUTDOWN; } } if (connState.compareTo(ConnectionHandshake.GRACEFUL_SHUTDOWN) >= 0) { for (;;) { final Command command = ioSession.poll(); if (command == null) { break; } if (command instanceof ShutdownCommand) { final ShutdownCommand shutdownCommand = (ShutdownCommand) command; if (shutdownCommand.getType() == CloseMode.IMMEDIATE) { connState = ConnectionHandshake.SHUTDOWN; } } else { command.cancel(); } } } if (connState.compareTo(ConnectionHandshake.SHUTDOWN) >= 0) { streams.shutdownAndReleaseAll(); ioSession.getLock().lock(); try { if (outputBuffer.isEmpty() && outputQueue.isEmpty()) { ioSession.close(); } } finally { ioSession.getLock().unlock(); } } } public final void onTimeout(final Timeout timeout) throws HttpException, IOException { connState = ConnectionHandshake.SHUTDOWN; final RawFrame goAway; if (localSettingState != SettingsHandshake.ACKED) { goAway = frameFactory.createGoAway(streams.getLastRemoteId(), H2Error.SETTINGS_TIMEOUT, "Setting timeout (" + timeout + ")"); } else { goAway = frameFactory.createGoAway(streams.getLastRemoteId(), H2Error.NO_ERROR, "Timeout due to inactivity (" + timeout + ")"); } commitFrame(goAway); for (final Iterator it = streams.iterator(); it.hasNext(); ) { final H2Stream stream = it.next(); stream.fail(new H2StreamResetException(H2Error.NO_ERROR, "Timeout due to inactivity (" + timeout + ")")); } streams.shutdownAndReleaseAll(); } public final void onDisconnect() { for (;;) { final AsyncPingHandler pingHandler = pingHandlers.poll(); if (pingHandler != null) { pingHandler.cancel(); } else { break; } } streams.shutdownAndReleaseAll(); CommandSupport.cancelCommands(ioSession); } private void executeShutdown(final ShutdownCommand shutdownCommand) throws IOException { if (shutdownCommand.getType() == CloseMode.IMMEDIATE) { streams.shutdownAndReleaseAll(); connState = ConnectionHandshake.SHUTDOWN; } else { if (connState.compareTo(ConnectionHandshake.ACTIVE) <= 0) { final RawFrame goAway = frameFactory.createGoAway(streams.getLastRemoteId(), H2Error.NO_ERROR, "Graceful shutdown"); commitFrame(goAway); connState = streams.isEmpty() ? ConnectionHandshake.SHUTDOWN : ConnectionHandshake.GRACEFUL_SHUTDOWN; } } } private void executePing(final PingCommand pingCommand) throws IOException { final AsyncPingHandler handler = pingCommand.getHandler(); pingHandlers.add(handler); final RawFrame ping = frameFactory.createPing(handler.getData()); commitFrame(ping); } private void executeStaleCheck(final StaleCheckCommand staleCheckCommand) { final Consumer callback = staleCheckCommand.getCallback(); callback.accept(ioSession.isOpen() && connState.compareTo(ConnectionHandshake.ACTIVE) == 0); } private void executeRequest(final RequestExecutionCommand requestExecutionCommand) throws IOException, HttpException { final int streamId = streams.generateStreamId(); final H2StreamChannel channel = createChannel(streamId); final H2Stream stream = streams.createActive(channel, outgoingRequest(channel, requestExecutionCommand.getExchangeHandler(), requestExecutionCommand.getPushHandlerFactory(), requestExecutionCommand.getContext())); if (streamListener != null) { final int initInputWindow = stream.getInputWindow().get(); streamListener.onInputFlowControl(this, streamId, initInputWindow, initInputWindow); final int initOutputWindow = stream.getOutputWindow().get(); streamListener.onOutputFlowControl(this, streamId, initOutputWindow, initOutputWindow); } if (stream.isOutputReady()) { stream.produceOutput(); } final CancellableDependency cancellableDependency = requestExecutionCommand.getCancellableDependency(); if (cancellableDependency != null) { cancellableDependency.setDependency(stream::abort); } } private void executePush(final PushResponseCommand pushResponseCommand) throws IOException, HttpException { if (pushResponseCommand.isCancelled()) { return; } final H2Stream stream = streams.lookupSeen(pushResponseCommand.getStreamId()); if (stream != null && stream.isReserved()) { if (!stream.isLocalClosed()) { stream.activate(); if (stream.isOutputReady()) { stream.produceOutput(); } } else { stream.abort(); } } } public final void onException(final Exception cause) { try { for (;;) { final AsyncPingHandler pingHandler = pingHandlers.poll(); if (pingHandler != null) { pingHandler.failed(cause); } else { break; } } CommandSupport.cancelCommands(ioSession); streams.shutdownAndReleaseAll(cause); if (!(cause instanceof ConnectionClosedException)) { if (connState.compareTo(ConnectionHandshake.GRACEFUL_SHUTDOWN) <= 0) { final H2Error errorCode; if (cause instanceof H2ConnectionException) { errorCode = H2Error.getByCode(((H2ConnectionException) cause).getCode()); } else if (cause instanceof ProtocolException) { errorCode = H2Error.PROTOCOL_ERROR; } else { errorCode = H2Error.INTERNAL_ERROR; } final RawFrame goAway = frameFactory.createGoAway(streams.getLastRemoteId(), errorCode, cause.getMessage()); commitFrame(goAway); } } } catch (final IOException ignore) { } finally { connState = ConnectionHandshake.SHUTDOWN; final CloseMode closeMode; if (cause instanceof ConnectionClosedException) { closeMode = CloseMode.GRACEFUL; } else if (cause instanceof SSLHandshakeException) { closeMode = CloseMode.GRACEFUL; } else if (cause instanceof IOException) { closeMode = CloseMode.IMMEDIATE; } else { closeMode = CloseMode.GRACEFUL; } ioSession.close(closeMode); } } private void consumeFrame(final RawFrame frame) throws HttpException, IOException { final FrameType frameType = FrameType.valueOf(frame.getType()); final int streamId = frame.getStreamId(); if (continuation != null && frameType != FrameType.CONTINUATION) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "CONTINUATION frame expected"); } switch (frameType) { case DATA: { if (streamId == 0) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal stream id: " + streamId); } final H2Stream stream = streams.lookupValid(streamId); try { consumeDataFrame(frame, stream); } catch (final H2StreamResetException ex) { stream.localReset(ex); } catch (final HttpStreamResetException ex) { stream.localReset(ex, ex.getCause() != null ? H2Error.INTERNAL_ERROR : H2Error.CANCEL); } if (stream.isClosed()) { stream.releaseResources(); requestSessionOutput(); } } break; case HEADERS: { if (streamId == 0) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal stream id: " + streamId); } H2Stream stream = streams.lookupValidOrNull(streamId); if (stream == null) { acceptHeaderFrame(); if (streams.isSameSide(streamId)) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal stream id: " + streamId); } if (goAwayReceived ) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "GOAWAY received"); } final H2StreamChannel channel = createChannel(streamId); if (connState.compareTo(ConnectionHandshake.ACTIVE) <= 0) { stream = streams.createActive(channel, incomingRequest(channel)); streams.resetIfExceedsMaxConcurrentLimit(stream, localConfig.getMaxConcurrentStreams()); } else { channel.localReset(H2Error.REFUSED_STREAM); stream = streams.createActive(channel, NoopH2StreamHandler.INSTANCE); } } else if (stream.isLocalClosed() && stream.isRemoteClosed()) { throw new H2ConnectionException(H2Error.STREAM_CLOSED, "Stream closed"); } else if (stream.isReserved()) { stream.activate(); streams.resetIfExceedsMaxConcurrentLimit(stream, localConfig.getMaxConcurrentStreams()); } try { consumeHeaderFrame(frame, stream); if (stream.isOutputReady()) { stream.produceOutput(); } } catch (final H2StreamResetException ex) { stream.localReset(ex); } catch (final HttpStreamResetException ex) { stream.localReset(ex, ex.getCause() != null ? H2Error.INTERNAL_ERROR : H2Error.CANCEL); } catch (final HttpException ex) { stream.handle(ex); } if (stream.isClosed()) { stream.releaseResources(); requestSessionOutput(); } } break; case CONTINUATION: { if (streamId == 0) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal stream id: " + streamId); } if (continuation == null) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Unexpected CONTINUATION frame"); } if (streamId != continuation.streamId) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Unexpected CONTINUATION stream id: " + streamId); } final H2Stream stream = streams.lookupValid(streamId); try { consumeContinuationFrame(frame, stream); } catch (final H2StreamResetException ex) { stream.localReset(ex); } catch (final HttpStreamResetException ex) { stream.localReset(ex, ex.getCause() != null ? H2Error.INTERNAL_ERROR : H2Error.CANCEL); } if (stream.isClosed()) { stream.releaseResources(); requestSessionOutput(); } } break; case WINDOW_UPDATE: { final ByteBuffer payload = frame.getPayload(); if (payload == null || payload.remaining() != 4) { throw new H2ConnectionException(H2Error.FRAME_SIZE_ERROR, "Invalid WINDOW_UPDATE frame payload"); } final int delta = payload.getInt(); if (delta <= 0) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Invalid WINDOW_UPDATE delta"); } if (streamId == 0) { try { updateOutputWindow(0, connOutputWindow, delta); } catch (final ArithmeticException ex) { throw new H2ConnectionException(H2Error.FLOW_CONTROL_ERROR, ex.getMessage()); } } else { final H2Stream stream = streams.lookup(streamId); if (stream != null) { try { updateOutputWindow(streamId, stream.getOutputWindow(), delta); } catch (final ArithmeticException ex) { throw new H2ConnectionException(H2Error.FLOW_CONTROL_ERROR, ex.getMessage()); } } } ioSession.setEvent(SelectionKey.OP_WRITE); } break; case RST_STREAM: { if (streamId == 0) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal stream id: " + streamId); } final H2Stream stream = streams.lookupSeen(streamId); if (stream != null) { final ByteBuffer payload = frame.getPayload(); if (payload == null || payload.remaining() != 4) { throw new H2ConnectionException(H2Error.FRAME_SIZE_ERROR, "Invalid RST_STREAM frame payload"); } final int errorCode = payload.getInt(); if (errorCode == H2Error.NO_ERROR.getCode() && allowGracefulAbort(stream)) { stream.abortGracefully(); requestSessionOutput(); } else { stream.fail(new H2StreamResetException(errorCode, "Stream reset (" + errorCode + ")")); requestSessionOutput(); } } } break; case PING: { if (streamId != 0) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal stream id"); } final ByteBuffer ping = frame.getPayloadContent(); if (ping == null || ping.remaining() != 8) { throw new H2ConnectionException(H2Error.FRAME_SIZE_ERROR, "Invalid PING frame payload"); } if (frame.isFlagSet(FrameFlag.ACK)) { final AsyncPingHandler pingHandler = pingHandlers.poll(); if (pingHandler != null) { pingHandler.consumeResponse(ping); } } else { final ByteBuffer pong = ByteBuffer.allocate(ping.remaining()); pong.put(ping); pong.flip(); final RawFrame response = frameFactory.createPingAck(pong); commitFrame(response); } } break; case SETTINGS: { if (streamId != 0) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal stream id"); } if (frame.isFlagSet(FrameFlag.ACK)) { if (localSettingState == SettingsHandshake.TRANSMITTED) { localSettingState = SettingsHandshake.ACKED; ioSession.setEvent(SelectionKey.OP_WRITE); applyLocalSettings(); } } else { final ByteBuffer payload = frame.getPayload(); if (payload != null) { if ((payload.remaining() % 6) != 0) { throw new H2ConnectionException(H2Error.FRAME_SIZE_ERROR, "Invalid SETTINGS payload"); } consumeSettingsFrame(payload); remoteSettingState = SettingsHandshake.TRANSMITTED; } final RawFrame response = frameFactory.createSettingsAck(); commitFrame(response); remoteSettingState = SettingsHandshake.ACKED; } } break; case PRIORITY: break; case PUSH_PROMISE: { acceptPushFrame(); if (streamId == 0) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal stream id: " + streamId); } if (goAwayReceived ) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "GOAWAY received"); } if (!localConfig.isPushEnabled()) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Push is disabled"); } final H2Stream stream = streams.lookupValid(streamId); if (stream.isRemoteClosed()) { stream.localReset(new H2StreamResetException(H2Error.STREAM_CLOSED, "Stream closed")); break; } final ByteBuffer payload = frame.getPayloadContent(); if (payload == null || payload.remaining() < 4) { throw new H2ConnectionException(H2Error.FRAME_SIZE_ERROR, "Invalid PUSH_PROMISE payload"); } final int promisedStreamId = payload.getInt(); if (promisedStreamId == 0 || streams.isSameSide(promisedStreamId)) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal promised stream id: " + promisedStreamId); } if (streams.lookupValidOrNull(promisedStreamId) != null) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Stream already open: " + promisedStreamId); } final H2StreamChannel channel = createChannel(promisedStreamId); final H2Stream promisedStream; if (connState.compareTo(ConnectionHandshake.ACTIVE) <= 0) { promisedStream = streams.createReserved(channel, incomingPushPromise(channel, stream.getPushHandlerFactory())); } else { channel.localReset(H2Error.REFUSED_STREAM); promisedStream = streams.createActive(channel, NoopH2StreamHandler.INSTANCE); } try { consumePushPromiseFrame(frame, payload, promisedStream); } catch (final H2StreamResetException ex) { promisedStream.localReset(ex); } catch (final HttpStreamResetException ex) { promisedStream.localReset(ex, ex.getCause() != null ? H2Error.INTERNAL_ERROR : H2Error.NO_ERROR); } } break; case GOAWAY: { if (streamId != 0) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal stream id"); } final ByteBuffer payload = frame.getPayload(); if (payload == null || payload.remaining() < 8) { throw new H2ConnectionException(H2Error.FRAME_SIZE_ERROR, "Invalid GOAWAY payload"); } final int processedLocalStreamId = payload.getInt(); final int errorCode = payload.getInt(); goAwayReceived = true; if (errorCode == H2Error.NO_ERROR.getCode()) { if (connState.compareTo(ConnectionHandshake.ACTIVE) <= 0) { for (final Iterator it = streams.iterator(); it.hasNext(); ) { final H2Stream stream = it.next(); final int activeStreamId = stream.getId(); if (!streams.isSameSide(activeStreamId) && activeStreamId > processedLocalStreamId) { stream.fail(new RequestNotExecutedException()); it.remove(); } } } connState = streams.isEmpty() ? ConnectionHandshake.SHUTDOWN : ConnectionHandshake.GRACEFUL_SHUTDOWN; } else { for (final Iterator it = streams.iterator(); it.hasNext(); ) { final H2Stream stream = it.next(); stream.fail(new H2StreamResetException(errorCode, "Connection terminated by the peer (" + errorCode + ")")); } streams.shutdownAndReleaseAll(); connState = ConnectionHandshake.SHUTDOWN; } } ioSession.setEvent(SelectionKey.OP_WRITE); break; case PRIORITY_UPDATE: { if (streamId != 0) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "PRIORITY_UPDATE must be on stream 0"); } final ByteBuffer payload = frame.getPayload(); if (payload == null || payload.remaining() < 4) { throw new H2ConnectionException(H2Error.FRAME_SIZE_ERROR, "Invalid PRIORITY_UPDATE payload"); } final int prioritizedId = payload.getInt() & 0x7fffffff; if (prioritizedId == 0) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "PRIORITY_UPDATE stream id is 0"); } final int len = payload.remaining(); final String field; if (len > 0) { final byte[] b = new byte[len]; payload.get(b); field = new String(b, StandardCharsets.US_ASCII); } else { field = ""; } final PriorityValue pv = parsePriorityValue(field); final H2Stream prioritizedStream = streams.lookupSeen(prioritizedId); if (prioritizedStream != null) { prioritizedStream.setPriorityValue(pv); } } break; } } private void consumeDataFrame(final RawFrame frame, final H2Stream stream) throws HttpException, IOException { if (stream.isRemoteClosed()) { throw new H2StreamResetException(H2Error.STREAM_CLOSED, "Stream already closed"); } final int streamId = stream.getId(); final ByteBuffer payload = frame.getPayloadContent(); if (payload != null) { final int frameLength = frame.getLength(); final int streamWinSize = updateInputWindow(streamId, stream.getInputWindow(), -frameLength); if (streamWinSize < lowMark && !stream.isRemoteClosed()) { stream.produceInputCapacityUpdate(); } final int connWinSize = updateInputWindow(0, connInputWindow, -frameLength); if (connWinSize < CONNECTION_WINDOW_LOW_MARK) { maximizeWindow(0, connInputWindow); } } stream.consumeData(payload, frame.isFlagSet(FrameFlag.END_STREAM)); } private void maximizeWindow(final int streamId, final AtomicInteger window) throws IOException { final int delta = updateWindowMax(window); if (delta > 0) { final RawFrame windowUpdateFrame = frameFactory.createWindowUpdate(streamId, delta); commitFrame(windowUpdateFrame); } } private void consumePushPromiseFrame(final RawFrame frame, final ByteBuffer payload, final H2Stream promisedStream) throws HttpException, IOException { final int promisedStreamId = promisedStream.getId(); if (!frame.isFlagSet(FrameFlag.END_HEADERS)) { continuation = new Continuation(promisedStreamId, frame.getType(), true, localConfig.getMaxContinuations()); } if (continuation == null) { final List
headers = hPackDecoder.decodeHeaders(payload); if (streamListener != null) { streamListener.onHeaderInput(this, promisedStreamId, headers); } promisedStream.consumePromise(headers); } else { continuation.copyPayload(payload); } } List
decodeHeaders(final ByteBuffer payload) throws HttpException { return hPackDecoder.decodeHeaders(payload); } private void consumeHeaderFrame(final RawFrame frame, final H2Stream stream) throws HttpException, IOException { if (stream.isRemoteClosed()) { throw new H2StreamResetException(H2Error.STREAM_CLOSED, "Stream already closed"); } final int streamId = stream.getId(); if (!frame.isFlagSet(FrameFlag.END_HEADERS)) { continuation = new Continuation(streamId, frame.getType(), frame.isFlagSet(FrameFlag.END_STREAM), localConfig.getMaxContinuations()); } final ByteBuffer payload = frame.getPayloadContent(); if (frame.isFlagSet(FrameFlag.PRIORITY)) { payload.getInt(); payload.get(); } if (continuation == null) { final List
headers = decodeHeaders(payload); if (streamListener != null) { streamListener.onHeaderInput(this, streamId, headers); } recordPriorityFromHeaders(stream, headers); stream.consumeHeader(headers, frame.isFlagSet(FrameFlag.END_STREAM)); } else { continuation.copyPayload(payload); } } private void consumeContinuationFrame(final RawFrame frame, final H2Stream stream) throws HttpException, IOException { if (stream.isRemoteClosed()) { throw new H2StreamResetException(H2Error.STREAM_CLOSED, "Stream already closed"); } final int streamId = frame.getStreamId(); final ByteBuffer payload = frame.getPayload(); continuation.copyPayload(payload); if (frame.isFlagSet(FrameFlag.END_HEADERS)) { final List
headers = decodeHeaders(continuation.getContent()); if (streamListener != null) { streamListener.onHeaderInput(this, streamId, headers); } recordPriorityFromHeaders(stream, headers); if (continuation.type == FrameType.PUSH_PROMISE.getValue()) { stream.consumePromise(headers); } else { stream.consumeHeader(headers, continuation.endStream); } continuation = null; } } private void consumeSettingsFrame(final ByteBuffer payload) throws IOException { final H2Config.Builder configBuilder = H2Config.initial(); while (payload.hasRemaining()) { final int code = payload.getShort(); final int value = payload.getInt(); final H2Param param = H2Param.valueOf(code); if (param != null) { validateSetting(param, value); switch (param) { case HEADER_TABLE_SIZE: try { configBuilder.setHeaderTableSize(value); } catch (final IllegalArgumentException ex) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, ex.getMessage()); } break; case MAX_CONCURRENT_STREAMS: try { configBuilder.setMaxConcurrentStreams(value); } catch (final IllegalArgumentException ex) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, ex.getMessage()); } break; case ENABLE_PUSH: configBuilder.setPushEnabled(value == 1); break; case INITIAL_WINDOW_SIZE: try { configBuilder.setInitialWindowSize(value); } catch (final IllegalArgumentException ex) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, ex.getMessage()); } break; case MAX_FRAME_SIZE: try { configBuilder.setMaxFrameSize(value); } catch (final IllegalArgumentException ex) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, ex.getMessage()); } break; case MAX_HEADER_LIST_SIZE: try { configBuilder.setMaxHeaderListSize(value); } catch (final IllegalArgumentException ex) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, ex.getMessage()); } break; case SETTINGS_NO_RFC7540_PRIORITIES: peerNoRfc7540Priorities = value == 1; break; } } } applyRemoteSettings(configBuilder.build()); } private void produceOutput() throws HttpException, IOException { for (final Iterator it = streams.iterator(); it.hasNext(); ) { final H2Stream stream = it.next(); if (!stream.isLocalClosed() && !stream.isReserved() && stream.getOutputWindow().get() > 0) { stream.produceOutput(); } if (stream.isClosedPastLingerDeadline()) { streams.dropStreamId(stream.getId()); stream.releaseResources(); it.remove(); requestSessionOutput(); } if (!outputQueue.isEmpty()) { break; } } } private void applyRemoteSettings(final H2Config config) throws H2ConnectionException { remoteConfig = config; hPackEncoder.setMaxTableSize(remoteConfig.getHeaderTableSize()); final int delta = remoteConfig.getInitialWindowSize() - initOutputWinSize; initOutputWinSize = remoteConfig.getInitialWindowSize(); final int maxFrameSize = remoteConfig.getMaxFrameSize(); if (maxFrameSize < outputBuffer.getMaxFramePayloadSize()) { try { outputBuffer.resize(maxFrameSize); } catch (final BufferOverflowException ex) { throw new H2ConnectionException(H2Error.INTERNAL_ERROR, "Failure resizing the frame output buffer"); } } if (delta != 0) { if (!streams.isEmpty()) { for (final Iterator it = streams.iterator(); it.hasNext(); ) { final H2Stream stream = it.next(); try { updateOutputWindow(stream.getId(), stream.getOutputWindow(), delta); } catch (final ArithmeticException ex) { throw new H2ConnectionException(H2Error.FLOW_CONTROL_ERROR, ex.getMessage()); } } } } } private void applyLocalSettings() throws H2ConnectionException { hPackDecoder.setMaxTableSize(localConfig.getHeaderTableSize()); hPackDecoder.setMaxListSize(localConfig.getMaxHeaderListSize()); final int delta = localConfig.getInitialWindowSize() - initInputWinSize; initInputWinSize = localConfig.getInitialWindowSize(); if (delta != 0 && !streams.isEmpty()) { for (final Iterator it = streams.iterator(); it.hasNext(); ) { final H2Stream stream = it.next(); try { updateInputWindow(stream.getId(), stream.getInputWindow(), delta); } catch (final ArithmeticException ex) { throw new H2ConnectionException(H2Error.FLOW_CONTROL_ERROR, ex.getMessage()); } } } lowMark = initInputWinSize / 2; } @Override public void close() throws IOException { ioSession.enqueue(ShutdownCommand.GRACEFUL, Command.Priority.IMMEDIATE); } @Override public void close(final CloseMode closeMode) { ioSession.close(closeMode); } @Override public boolean isOpen() { return connState == ConnectionHandshake.ACTIVE; } @Override public void setSocketTimeout(final Timeout timeout) { ioSession.setSocketTimeout(timeout); } @Override public SSLSession getSSLSession() { final TlsDetails tlsDetails = ioSession.getTlsDetails(); return tlsDetails != null ? tlsDetails.getSSLSession() : null; } @Override public EndpointDetails getEndpointDetails() { if (endpointDetails == null) { endpointDetails = new BasicEndpointDetails( ioSession.getRemoteAddress(), ioSession.getLocalAddress(), connMetrics, ioSession.getSocketTimeout()); } return endpointDetails; } @Override public Timeout getSocketTimeout() { return ioSession.getSocketTimeout(); } @Override public ProtocolVersion getProtocolVersion() { return HttpVersion.HTTP_2; } @Override public SocketAddress getRemoteAddress() { return ioSession.getRemoteAddress(); } @Override public SocketAddress getLocalAddress() { return ioSession.getLocalAddress(); } void appendState(final StringBuilder buf) { buf.append("connState=").append(connState) .append(", connInputWindow=").append(connInputWindow) .append(", connOutputWindow=").append(connOutputWindow) .append(", outputQueue=").append(outputQueue.size()) .append(", streams.localCoubt=").append(streams.getLocalCount()) .append(", streams.remoteCount=").append(streams.getRemoteCount()) .append(", streams.lastLocal=").append(streams.getLastLocalId()) .append(", streams.lastRemote=").append(streams.getLastRemoteId()); } private static class Continuation { final int streamId; final int type; final boolean endStream; final ByteArrayBuffer headerBuffer; final int maxContinuation; final boolean enforceMacContinuations; private int count; private Continuation(final int streamId, final int type, final boolean endStream, final int maxContinuation) { this.streamId = streamId; this.type = type; this.endStream = endStream; this.maxContinuation = maxContinuation; this.enforceMacContinuations = maxContinuation < Integer.MAX_VALUE; this.headerBuffer = new ByteArrayBuffer(1024); } void copyPayload(final ByteBuffer payload) throws H2ConnectionException { if (payload == null) { return; } if (enforceMacContinuations && count > maxContinuation) { throw new H2ConnectionException(H2Error.ENHANCE_YOUR_CALM, "Excessive number of continuation frames"); } count++; final int originalLength = headerBuffer.length(); final int toCopy = payload.remaining(); headerBuffer.ensureCapacity(toCopy); payload.get(headerBuffer.array(), originalLength, toCopy); headerBuffer.setLength(originalLength + toCopy); } ByteBuffer getContent() { return ByteBuffer.wrap(headerBuffer.array(), 0, headerBuffer.length()); } } H2StreamChannel createChannel(final int streamId) { return new H2StreamChannelImpl(streamId, initInputWinSize, initOutputWinSize); } H2Stream createStream(final H2StreamChannel channel, final H2StreamHandler streamHandler) throws H2ConnectionException { return streams.createActive(channel, streamHandler); } private void recordPriorityFromHeaders(final H2Stream stream, final List headers) { if (headers == null || headers.isEmpty()) { return; } for (final Header h : headers) { if (HttpHeaders.PRIORITY.equalsIgnoreCase(h.getName())) { final PriorityValue pv = parsePriorityValue(h); stream.setPriorityValue(pv); break; } } } private PriorityValue parsePriorityValue(final Header header) { return PriorityParamsParser.parse(header).toValueWithDefaults(); } private PriorityValue parsePriorityValue(final String field) { return PriorityParamsParser.parse(field).toValueWithDefaults(); } class H2StreamChannelImpl implements H2StreamChannel { private final int id; private final AtomicInteger inputWindow; private final AtomicInteger outputWindow; private volatile boolean localClosed; private volatile long localResetTime; H2StreamChannelImpl(final int id, final int initialInputWindowSize, final int initialOutputWindowSize) { this.id = id; this.inputWindow = new AtomicInteger(initialInputWindowSize); this.outputWindow = new AtomicInteger(initialOutputWindowSize); } @Override public int getId() { return id; } @Override public AtomicInteger getOutputWindow() { return outputWindow; } @Override public AtomicInteger getInputWindow() { return inputWindow; } void ensureNotClosed() throws H2ConnectionException { if (localClosed) { throw new H2ConnectionException(H2Error.INTERNAL_ERROR, "Stream already closed locally"); } } @Override public void submit(final List
headers, final boolean endStream) throws IOException { ioSession.getLock().lock(); try { if (headers == null || headers.isEmpty()) { throw new H2ConnectionException(H2Error.INTERNAL_ERROR, "Message headers are missing"); } if (isLocalReset()) { return; } ensureNotClosed(); if (peerNoRfc7540Priorities) { for (final Header h : headers) { if (HttpHeaders.PRIORITY.equalsIgnoreCase(h.getName())) { final byte[] ascii = h.getValue() != null ? h.getValue().getBytes(StandardCharsets.US_ASCII) : new byte[0]; final int sid = id & 0x7fffffff; final ByteArrayBuffer b = new ByteArrayBuffer(4 + ascii.length); b.append((byte) (sid >> 24)); b.append((byte) (sid >> 16)); b.append((byte) (sid >> 8)); b.append((byte) sid); b.append(ascii, 0, ascii.length); final ByteBuffer pl = ByteBuffer.wrap(b.array(), 0, b.length()); final RawFrame priUpd = new RawFrame(FrameType.PRIORITY_UPDATE.getValue(), 0, 0, pl); commitFrameInternal(priUpd); break; } } } commitHeaders(id, headers, endStream); if (endStream) { localClosed = true; } } finally { ioSession.getLock().unlock(); } } @Override public void push(final List
headers, final AsyncPushProducer pushProducer) throws HttpException, IOException { acceptPushRequest(); ioSession.getLock().lock(); try { if (isLocalReset()) { return; } ensureNotClosed(); final int promisedStreamId = streams.generateStreamId(); final H2StreamChannel channel = createChannel(promisedStreamId); final H2Stream stream = streams.createReserved(channel, outgoingPushPromise(channel, pushProducer)); commitPushPromise(id, promisedStreamId, headers); stream.markRemoteClosed(); submitCommand(new PushResponseCommand(promisedStreamId)); } finally { ioSession.getLock().unlock(); } } @Override public void update(final int increment) throws IOException { incrementInputCapacity(id, inputWindow, increment); } @Override public int write(final ByteBuffer payload) throws IOException { ioSession.getLock().lock(); try { if (isLocalReset()) { return 0; } ensureNotClosed(); return streamData(id, outputWindow, payload); } finally { ioSession.getLock().unlock(); } } @Override public void endStream(final List trailers) throws IOException { ioSession.getLock().lock(); try { if (isLocalReset()) { return; } ensureNotClosed(); localClosed = true; if (trailers != null && !trailers.isEmpty()) { commitHeaders(id, trailers, true); } else { final RawFrame frame = frameFactory.createData(id, null, true); commitFrameInternal(frame); } } finally { ioSession.getLock().unlock(); } } @Override public void endStream() throws IOException { endStream(null); } @Override public void requestOutput() { requestSessionOutput(); } @Override public boolean isLocalClosed() { return localClosed; } @Override public void markLocalClosed() { localClosed = true; } @Override public boolean localReset(final int code) throws IOException { ioSession.getLock().lock(); try { if (isLocalReset()) { return false; } localClosed = true; localResetTime = System.currentTimeMillis(); final RawFrame resetStream = frameFactory.createResetStream(id, code); commitFrameInternal(resetStream); return true; } finally { ioSession.getLock().unlock(); } } @Override public long getLocalResetTime() { return localResetTime; } @Override public boolean cancel() { try { return localReset(H2Error.CANCEL); } catch (final IOException ignore) { return false; } } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append("[") .append("id=").append(id) .append(", connState=").append(connState) .append(", inputWindow=").append(inputWindow) .append(", outputWindow=").append(outputWindow) .append(", localClosed=").append(localClosed) .append("]"); return buf.toString(); } } }httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2IOEventHandler.java0100644 0000000 0000000 00000004021 00000250600 027651 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import org.apache.hc.core5.net.InetAddressUtils; /** * {@link org.apache.hc.core5.reactor.IOEventHandler} that implements * client side HTTP/2 messaging protocol with full support for * multiplexed message transmission. * * @since 5.0 */ public class ClientH2IOEventHandler extends AbstractH2IOEventHandler { public ClientH2IOEventHandler(final ClientH2StreamMultiplexer streamMultiplexer) { super(streamMultiplexer); } @Override public String toString() { final StringBuilder buf = new StringBuilder(); InetAddressUtils.formatAddress(buf, getLocalAddress()); buf.append("->"); InetAddressUtils.formatAddress(buf, getRemoteAddress()); buf.append(" ["); streamMultiplexer.appendState(buf); buf.append("]"); return buf.toString(); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2PrefaceHandler.java0100644 0000000 0000000 00000014615 00000250600 027717 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.impl.nio.BufferedData; import org.apache.hc.core5.http2.ssl.ApplicationProtocol; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.reactor.ssl.TlsDetails; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TextUtils; /** * I/O event handler for events fired by {@link ProtocolIOSession} that implements * client side of the HTTP/2 protocol negotiation handshake always forcing the choice * of HTTP/2. * * @since 5.2 */ @Internal public class ClientH2PrefaceHandler extends PrefaceHandlerBase { // PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n final static byte[] PREFACE = new byte[] { 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a}; private final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory; private final boolean strictALPNHandshake; private final AtomicBoolean initialized; private volatile ByteBuffer preface; private volatile BufferedData inBuf; public ClientH2PrefaceHandler( final ProtocolIOSession ioSession, final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory, final boolean strictALPNHandshake, final Callback exceptionCallback) { this(ioSession, http2StreamHandlerFactory, strictALPNHandshake, null, exceptionCallback); } /** * @since 5.1 */ public ClientH2PrefaceHandler( final ProtocolIOSession ioSession, final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory, final boolean strictALPNHandshake, final FutureCallback resultCallback, final Callback exceptionCallback) { super(ioSession, resultCallback, exceptionCallback); this.http2StreamHandlerFactory = Args.notNull(http2StreamHandlerFactory, "HTTP/2 stream handler factory"); this.strictALPNHandshake = strictALPNHandshake; this.initialized = new AtomicBoolean(); } private void initialize() throws IOException { final TlsDetails tlsDetails = ioSession.getTlsDetails(); if (tlsDetails != null) { final String applicationProtocol = tlsDetails.getApplicationProtocol(); if (TextUtils.isEmpty(applicationProtocol)) { if (strictALPNHandshake) { throw new ProtocolNegotiationException("ALPN: missing application protocol"); } } else { if (!ApplicationProtocol.HTTP_2.id.equals(applicationProtocol)) { throw new ProtocolNegotiationException("ALPN: unexpected application protocol '" + applicationProtocol + "'"); } } } this.preface = ByteBuffer.wrap(PREFACE); ioSession.setEvent(SelectionKey.OP_WRITE); } /** * @return true if the entire preface has been written out */ private boolean writeOutPreface(final IOSession session, final ByteBuffer preface) throws IOException { if (preface.hasRemaining()) { session.write(preface); } if (!preface.hasRemaining()) { session.clearEvent(SelectionKey.OP_WRITE); final ByteBuffer data = inBuf != null ? inBuf.data() : null; startProtocol(new ClientH2IOEventHandler(http2StreamHandlerFactory.create(ioSession)), data); if (inBuf != null) { inBuf.clear(); } return true; } return false; } @Override public void connected(final IOSession session) throws IOException { if (initialized.compareAndSet(false, true)) { initialize(); } } @Override public void outputReady(final IOSession session) throws IOException { if (initialized.compareAndSet(false, true)) { initialize(); } final ByteBuffer preface = this.preface; if (preface != null) { if (writeOutPreface(session, preface)) { this.preface = null; } } else { throw new ProtocolNegotiationException("Unexpected output"); } } @Override public void inputReady(final IOSession session, final ByteBuffer src) throws IOException { if (src != null) { if (inBuf == null) { inBuf = BufferedData.allocate(src.remaining()); } inBuf.put(src); } final ByteBuffer preface = this.preface; if (preface != null) { if (writeOutPreface(session, preface)) { this.preface = null; } } else { throw new ProtocolNegotiationException("Unexpected input"); } } @Override public String toString() { return getClass().getName(); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2StreamHandler.java0100644 0000000 0000000 00000025614 00000250600 027606 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.impl.BasicHttpConnectionMetrics; import org.apache.hc.core5.http.impl.IncomingEntityDetails; import org.apache.hc.core5.http.impl.nio.MessageState; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http2.H2ConnectionException; import org.apache.hc.core5.http2.H2Error; import org.apache.hc.core5.http2.impl.DefaultH2RequestConverter; import org.apache.hc.core5.http2.impl.DefaultH2ResponseConverter; class ClientH2StreamHandler implements H2StreamHandler { private final H2StreamChannel outputChannel; private final DataStreamChannel dataChannel; private final HttpProcessor httpProcessor; private final BasicHttpConnectionMetrics connMetrics; private final AsyncClientExchangeHandler exchangeHandler; private final HandlerFactory pushHandlerFactory; private final HttpCoreContext context; private final AtomicReference requestState; private final AtomicReference responseState; private final AtomicBoolean requestCommitted; private final AtomicBoolean failed; private final AtomicBoolean done; ClientH2StreamHandler( final H2StreamChannel outputChannel, final HttpProcessor httpProcessor, final BasicHttpConnectionMetrics connMetrics, final AsyncClientExchangeHandler exchangeHandler, final HandlerFactory pushHandlerFactory, final HttpCoreContext context) { this.outputChannel = outputChannel; this.dataChannel = new DataStreamChannel() { @Override public void requestOutput() { outputChannel.requestOutput(); } @Override public int write(final ByteBuffer src) throws IOException { return outputChannel.write(src); } @Override public void endStream(final List trailers) throws IOException { outputChannel.endStream(trailers); requestState.set(MessageState.COMPLETE); } @Override public void endStream() throws IOException { outputChannel.endStream(); requestState.set(MessageState.COMPLETE); } }; this.httpProcessor = httpProcessor; this.connMetrics = connMetrics; this.exchangeHandler = exchangeHandler; this.pushHandlerFactory = pushHandlerFactory; this.context = context; this.requestCommitted = new AtomicBoolean(); this.failed = new AtomicBoolean(); this.done = new AtomicBoolean(); this.requestState = new AtomicReference<>(MessageState.HEADERS); this.responseState = new AtomicReference<>(MessageState.HEADERS); } @Override public HandlerFactory getPushHandlerFactory() { return pushHandlerFactory; } @Override public boolean isOutputReady() { switch (requestState.get()) { case HEADERS: return true; case BODY: return exchangeHandler.available() > 0; default: return false; } } private void commitRequest(final HttpRequest request, final EntityDetails entityDetails) throws HttpException, IOException { if (requestCommitted.compareAndSet(false, true)) { context.setProtocolVersion(HttpVersion.HTTP_2); context.setRequest(request); httpProcessor.process(request, entityDetails, context); final List
headers = DefaultH2RequestConverter.INSTANCE.convert(request); if (entityDetails == null) { requestState.set(MessageState.COMPLETE); outputChannel.submit(headers, true); connMetrics.incrementRequestCount(); } else { outputChannel.submit(headers, false); connMetrics.incrementRequestCount(); final Header h = request.getFirstHeader(HttpHeaders.EXPECT); final boolean expectContinue = h != null && HeaderElements.CONTINUE.equalsIgnoreCase(h.getValue()); if (expectContinue) { requestState.set(MessageState.ACK); } else { exchangeHandler.produce(dataChannel); if (requestState.compareAndSet(MessageState.HEADERS, MessageState.BODY)) { outputChannel.requestOutput(); } } } } else { throw new H2ConnectionException(H2Error.INTERNAL_ERROR, "Request already committed"); } } @Override public void produceOutput() throws HttpException, IOException { switch (requestState.get()) { case HEADERS: exchangeHandler.produceRequest((request, entityDetails, httpContext) -> commitRequest(request, entityDetails), context); break; case BODY: exchangeHandler.produce(dataChannel); break; } } @Override public void consumePromise(final List
headers) throws HttpException, IOException { throw new ProtocolException("Unexpected message promise"); } @Override public void consumeHeader(final List
headers, final boolean endStream) throws HttpException, IOException { if (done.get()) { throw new ProtocolException("Unexpected message headers"); } switch (responseState.get()) { case HEADERS: final HttpResponse response = DefaultH2ResponseConverter.INSTANCE.convert(headers); final int status = response.getCode(); if (status < HttpStatus.SC_INFORMATIONAL) { throw new ProtocolException("Invalid response: " + new StatusLine(response)); } if (status > HttpStatus.SC_CONTINUE && status < HttpStatus.SC_SUCCESS) { exchangeHandler.consumeInformation(response, context); } if (requestState.get() == MessageState.ACK) { if (status == HttpStatus.SC_CONTINUE || status >= HttpStatus.SC_SUCCESS) { requestState.set(MessageState.BODY); exchangeHandler.produce(dataChannel); } } if (status < HttpStatus.SC_SUCCESS) { return; } final EntityDetails entityDetails = endStream ? null : new IncomingEntityDetails(response, -1); context.setResponse(response); httpProcessor.process(response, entityDetails, context); connMetrics.incrementResponseCount(); exchangeHandler.consumeResponse(response, entityDetails, context); responseState.set(endStream ? MessageState.COMPLETE : MessageState.BODY); break; case BODY: responseState.set(MessageState.COMPLETE); exchangeHandler.streamEnd(headers); break; default: throw new ProtocolException("Unexpected message headers"); } } @Override public void updateInputCapacity() throws IOException { exchangeHandler.updateCapacity(outputChannel); } @Override public void consumeData(final ByteBuffer src, final boolean endStream) throws HttpException, IOException { if (done.get() || responseState.get() != MessageState.BODY) { throw new ProtocolException("Unexpected message data"); } if (src != null) { exchangeHandler.consume(src); } if (endStream) { responseState.set(MessageState.COMPLETE); exchangeHandler.streamEnd(null); } } @Override public void handle(final HttpException ex, final boolean endStream) throws HttpException, IOException { throw ex; } @Override public void failed(final Exception cause) { try { if (failed.compareAndSet(false, true)) { if (exchangeHandler != null) { exchangeHandler.failed(cause); } } } finally { releaseResources(); } } @Override public void releaseResources() { if (done.compareAndSet(false, true)) { responseState.set(MessageState.COMPLETE); requestState.set(MessageState.COMPLETE); exchangeHandler.releaseResources(); } } @Override public String toString() { return "[" + "requestState=" + requestState.get() + ", responseState=" + responseState.get() + ']'; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2StreamMultiplexer.java0100644 0000000 0000000 00000016433 00000250600 030542 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.AsyncPushProducer; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http2.H2ConnectionException; import org.apache.hc.core5.http2.H2Error; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.config.H2Param; import org.apache.hc.core5.http2.config.H2Setting; import org.apache.hc.core5.http2.frame.DefaultFrameFactory; import org.apache.hc.core5.http2.frame.FrameFactory; import org.apache.hc.core5.http2.frame.StreamIdGenerator; import org.apache.hc.core5.reactor.ProtocolIOSession; /** * I/O event handler for events fired by {@link ProtocolIOSession} that implements * client side HTTP/2 messaging protocol with full support for * multiplexed message transmission. * * @since 5.0 */ @Internal public class ClientH2StreamMultiplexer extends AbstractH2StreamMultiplexer { private final HandlerFactory pushHandlerFactory; public ClientH2StreamMultiplexer( final ProtocolIOSession ioSession, final FrameFactory frameFactory, final HttpProcessor httpProcessor, final HandlerFactory pushHandlerFactory, final H2Config h2Config, final CharCodingConfig charCodingConfig, final H2StreamListener streamListener) { super(ioSession, frameFactory, StreamIdGenerator.ODD, httpProcessor, charCodingConfig, h2Config, streamListener); this.pushHandlerFactory = pushHandlerFactory; } public ClientH2StreamMultiplexer( final ProtocolIOSession ioSession, final HttpProcessor httpProcessor, final HandlerFactory pushHandlerFactory, final H2Config h2Config, final CharCodingConfig charCodingConfig) { this(ioSession, DefaultFrameFactory.INSTANCE, httpProcessor, pushHandlerFactory, h2Config, charCodingConfig, null); } public ClientH2StreamMultiplexer( final ProtocolIOSession ioSession, final HttpProcessor httpProcessor, final H2Config h2Config, final CharCodingConfig charCodingConfig) { this(ioSession, httpProcessor, null, h2Config, charCodingConfig); } @Override void validateSetting(final H2Param param, final int value) throws H2ConnectionException { if (param == H2Param.ENABLE_PUSH && value == 1) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal ENABLE_PUSH setting"); } } @Override H2Setting[] generateSettings(final H2Config localConfig) { return new H2Setting[] { new H2Setting(H2Param.HEADER_TABLE_SIZE, localConfig.getHeaderTableSize()), new H2Setting(H2Param.ENABLE_PUSH, localConfig.isPushEnabled() ? 1 : 0), new H2Setting(H2Param.MAX_CONCURRENT_STREAMS, localConfig.getMaxConcurrentStreams()), new H2Setting(H2Param.INITIAL_WINDOW_SIZE, localConfig.getInitialWindowSize()), new H2Setting(H2Param.MAX_FRAME_SIZE, localConfig.getMaxFrameSize()), new H2Setting(H2Param.MAX_HEADER_LIST_SIZE, localConfig.getMaxHeaderListSize()), new H2Setting(H2Param.SETTINGS_NO_RFC7540_PRIORITIES, 1) }; } @Override void acceptHeaderFrame() throws H2ConnectionException { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal HEADERS frame"); } @Override void acceptPushFrame() { } @Override void acceptPushRequest() throws H2ConnectionException { throw new H2ConnectionException(H2Error.INTERNAL_ERROR, "Illegal attempt to push a response"); } @Override H2StreamHandler outgoingRequest( final H2StreamChannel channel, final AsyncClientExchangeHandler exchangeHandler, final HandlerFactory pushHandlerFactory, final HttpContext context) { final HttpCoreContext coreContext = HttpCoreContext.castOrCreate(context); coreContext.setSSLSession(getSSLSession()); coreContext.setEndpointDetails(getEndpointDetails()); return new ClientH2StreamHandler(channel, getHttpProcessor(), getConnMetrics(), exchangeHandler, pushHandlerFactory != null ? pushHandlerFactory : this.pushHandlerFactory, coreContext); } @Override H2StreamHandler incomingRequest(final H2StreamChannel channel) throws IOException { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal incoming request"); } @Override H2StreamHandler outgoingPushPromise(final H2StreamChannel channel, final AsyncPushProducer pushProducer) throws IOException { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal attempt to send push promise"); } @Override H2StreamHandler incomingPushPromise(final H2StreamChannel channel, final HandlerFactory pushHandlerFactory) { final HttpCoreContext context = HttpCoreContext.create(); context.setSSLSession(getSSLSession()); context.setEndpointDetails(getEndpointDetails()); return new ClientPushH2StreamHandler(channel, getHttpProcessor(), getConnMetrics(), pushHandlerFactory != null ? pushHandlerFactory : this.pushHandlerFactory, context); } @Override boolean allowGracefulAbort(final H2Stream stream) { return stream.isRemoteClosed() && !stream.isLocalClosed(); } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append("["); appendState(buf); buf.append("]"); return buf.toString(); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2StreamMultiplexerFactory.java0100644 0000000 0000000 00000010403 00000250600 032061 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.frame.DefaultFrameFactory; import org.apache.hc.core5.http2.frame.FrameFactory; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.util.Args; /** * {@link ClientH2StreamMultiplexer} factory. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) @Internal public final class ClientH2StreamMultiplexerFactory { private final HttpProcessor httpProcessor; private final HandlerFactory pushHandlerFactory; private final H2Config h2Config; private final CharCodingConfig charCodingConfig; private final H2StreamListener streamListener; private final FrameFactory frameFactory; public ClientH2StreamMultiplexerFactory( final HttpProcessor httpProcessor, final HandlerFactory pushHandlerFactory, final H2Config h2Config, final CharCodingConfig charCodingConfig, final H2StreamListener streamListener, final FrameFactory frameFactory) { this.httpProcessor = Args.notNull(httpProcessor, "HTTP processor"); this.pushHandlerFactory = pushHandlerFactory; this.h2Config = h2Config != null ? h2Config : H2Config.DEFAULT; this.charCodingConfig = charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT; this.streamListener = streamListener; this.frameFactory = frameFactory != null ? frameFactory : DefaultFrameFactory.INSTANCE; } public ClientH2StreamMultiplexerFactory( final HttpProcessor httpProcessor, final HandlerFactory pushHandlerFactory, final H2Config h2Config, final CharCodingConfig charCodingConfig, final H2StreamListener streamListener ) { this(httpProcessor, pushHandlerFactory, h2Config, charCodingConfig, streamListener, null); } public ClientH2StreamMultiplexerFactory( final HttpProcessor httpProcessor, final HandlerFactory pushHandlerFactory, final H2StreamListener streamListener) { this(httpProcessor, pushHandlerFactory, null, null, streamListener, null); } public ClientH2StreamMultiplexerFactory( final HttpProcessor httpProcessor, final H2StreamListener streamListener) { this(httpProcessor, null, streamListener); } public ClientH2StreamMultiplexer create(final ProtocolIOSession ioSession) { return new ClientH2StreamMultiplexer(ioSession, frameFactory, httpProcessor, pushHandlerFactory, h2Config, charCodingConfig, streamListener); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2UpgradeHandler.java0100644 0000000 0000000 00000006001 00000250600 027727 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.impl.nio.HttpConnectionEventHandler; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.reactor.ProtocolUpgradeHandler; import org.apache.hc.core5.util.Args; /** * Protocol upgrade handler that upgrades the underlying {@link ProtocolIOSession} * to HTTP/2 in case of a successful protocol negotiation. * * @since 5.2 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) @Internal public class ClientH2UpgradeHandler implements ProtocolUpgradeHandler { private final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory; private final Callback exceptionCallback; public ClientH2UpgradeHandler(final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory, final Callback exceptionCallback) { this.http2StreamHandlerFactory = Args.notNull(http2StreamHandlerFactory, "HTTP/2 stream handler factory"); this.exceptionCallback = exceptionCallback; } @Override public void upgrade(final ProtocolIOSession ioSession, final FutureCallback callback) { final HttpConnectionEventHandler protocolNegotiator = new ClientH2PrefaceHandler( ioSession, http2StreamHandlerFactory, true, callback, exceptionCallback); ioSession.upgrade(protocolNegotiator); try { protocolNegotiator.connected(ioSession); } catch (final IOException ex) { protocolNegotiator.exception(ioSession, ex); } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttp1UpgradeHandler.java0100644 0000000 0000000 00000005643 00000250600 030471 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.impl.nio.ClientHttp1IOEventHandler; import org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexerFactory; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.reactor.ProtocolUpgradeHandler; import org.apache.hc.core5.util.Args; /** * Protocol upgrade handler that upgrades the underlying {@link ProtocolIOSession} * to HTTP/1.1 in case of a successful protocol negotiation or as a default fall-back. * * @since 5.2 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) @Internal public class ClientHttp1UpgradeHandler implements ProtocolUpgradeHandler { private final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory; public ClientHttp1UpgradeHandler(final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory) { this.http1StreamHandlerFactory = Args.notNull(http1StreamHandlerFactory, "HTTP/1.1 stream handler factory"); } @Override public void upgrade(final ProtocolIOSession ioSession, final FutureCallback callback) { final ClientHttp1IOEventHandler eventHandler = new ClientHttp1IOEventHandler(http1StreamHandlerFactory.create(ioSession)); ioSession.upgrade(eventHandler); try { eventHandler.connected(ioSession); if (callback != null) { callback.completed(ioSession); } } catch (final IOException ex) { eventHandler.exception(ioSession, ex); } } } ././@LongLink0100644 0000000 0000000 00000000150 00000250600 011605 Lustar 0000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttpProtocolNegotiationStarter.javahttpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttpProtocolNegotiationStarter.j0100644 0000000 0000000 00000012003 00000250600 032366 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.nio.ClientHttp1IOEventHandler; import org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexerFactory; import org.apache.hc.core5.http.impl.nio.HttpConnectionEventHandler; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.ssl.ApplicationProtocol; import org.apache.hc.core5.reactor.EndpointParameters; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; /** * Client I/O event starter that prepares I/O sessions for an initial protocol handshake. * This class may return a different {@link org.apache.hc.core5.reactor.IOEventHandler} * implementation based on the current HTTP version policy. * * @since 5.1 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) @Internal public class ClientHttpProtocolNegotiationStarter implements IOEventHandlerFactory { private final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory; private final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory; private final HttpVersionPolicy versionPolicy; private final TlsStrategy tlsStrategy; private final Timeout handshakeTimeout; private final Callback exceptionCallback; public ClientHttpProtocolNegotiationStarter( final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory, final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory, final HttpVersionPolicy versionPolicy, final TlsStrategy tlsStrategy, final Timeout handshakeTimeout, final Callback exceptionCallback) { this.http1StreamHandlerFactory = Args.notNull(http1StreamHandlerFactory, "HTTP/1.1 stream handler factory"); this.http2StreamHandlerFactory = Args.notNull(http2StreamHandlerFactory, "HTTP/2 stream handler factory"); this.versionPolicy = versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE; this.tlsStrategy = tlsStrategy; this.handshakeTimeout = handshakeTimeout; this.exceptionCallback = exceptionCallback; } @Override public HttpConnectionEventHandler createHandler(final ProtocolIOSession ioSession, final Object attachment) { HttpVersionPolicy endpointPolicy = versionPolicy; if (attachment instanceof EndpointParameters) { final EndpointParameters params = (EndpointParameters) attachment; if (params.getAttachment() instanceof HttpVersionPolicy) { endpointPolicy = (HttpVersionPolicy) params.getAttachment(); } if (tlsStrategy != null && URIScheme.HTTPS.same(params.getScheme())) { tlsStrategy.upgrade(ioSession, params, endpointPolicy, handshakeTimeout, null); } } ioSession.registerProtocol(ApplicationProtocol.HTTP_1_1.id, new ClientHttp1UpgradeHandler(http1StreamHandlerFactory)); ioSession.registerProtocol(ApplicationProtocol.HTTP_2.id, new ClientH2UpgradeHandler(http2StreamHandlerFactory, exceptionCallback)); switch (endpointPolicy) { case FORCE_HTTP_2: return new ClientH2PrefaceHandler(ioSession, http2StreamHandlerFactory, false, exceptionCallback); case FORCE_HTTP_1: return new ClientHttp1IOEventHandler(http1StreamHandlerFactory.create(ioSession)); default: return new HttpProtocolNegotiator(ioSession, null); } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientPushH2StreamHandler.java0100644 0000000 0000000 00000017670 00000250600 030451 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.impl.BasicHttpConnectionMetrics; import org.apache.hc.core5.http.impl.IncomingEntityDetails; import org.apache.hc.core5.http.impl.nio.MessageState; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http2.H2ConnectionException; import org.apache.hc.core5.http2.H2Error; import org.apache.hc.core5.http2.H2StreamResetException; import org.apache.hc.core5.http2.impl.DefaultH2RequestConverter; import org.apache.hc.core5.http2.impl.DefaultH2ResponseConverter; import org.apache.hc.core5.util.Asserts; class ClientPushH2StreamHandler implements H2StreamHandler { private final H2StreamChannel internalOutputChannel; private final HttpProcessor httpProcessor; private final BasicHttpConnectionMetrics connMetrics; private final HandlerFactory pushHandlerFactory; private final HttpCoreContext context; private final AtomicBoolean failed; private final AtomicBoolean done; private volatile HttpRequest request; private volatile AsyncPushConsumer exchangeHandler; private volatile MessageState requestState; private volatile MessageState responseState; ClientPushH2StreamHandler( final H2StreamChannel outputChannel, final HttpProcessor httpProcessor, final BasicHttpConnectionMetrics connMetrics, final HandlerFactory pushHandlerFactory, final HttpCoreContext context) { this.internalOutputChannel = outputChannel; this.httpProcessor = httpProcessor; this.connMetrics = connMetrics; this.pushHandlerFactory = pushHandlerFactory; this.context = context; this.failed = new AtomicBoolean(); this.done = new AtomicBoolean(); this.requestState = MessageState.HEADERS; this.responseState = MessageState.HEADERS; } @Override public HandlerFactory getPushHandlerFactory() { return pushHandlerFactory; } @Override public boolean isOutputReady() { return false; } @Override public void produceOutput() throws HttpException, IOException { } @Override public void consumePromise(final List
headers) throws HttpException, IOException { if (requestState == MessageState.HEADERS) { request = DefaultH2RequestConverter.INSTANCE.convert(headers); try { exchangeHandler = pushHandlerFactory != null ? pushHandlerFactory.create(request, context) : null; } catch (final ProtocolException ex) { exchangeHandler = new NoopAsyncPushHandler(); throw new H2StreamResetException(H2Error.PROTOCOL_ERROR, ex.getMessage()); } if (exchangeHandler == null) { exchangeHandler = new NoopAsyncPushHandler(); throw new H2StreamResetException(H2Error.REFUSED_STREAM, "Stream refused"); } context.setProtocolVersion(HttpVersion.HTTP_2); context.setRequest(request); httpProcessor.process(request, null, context); connMetrics.incrementRequestCount(); this.requestState = MessageState.COMPLETE; } else { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Unexpected promise"); } } @Override public void consumeHeader(final List
headers, final boolean endStream) throws HttpException, IOException { if (responseState == MessageState.HEADERS) { Asserts.notNull(request, "Request"); Asserts.notNull(exchangeHandler, "Exchange handler"); final HttpResponse response = DefaultH2ResponseConverter.INSTANCE.convert(headers); final EntityDetails entityDetails = endStream ? null : new IncomingEntityDetails(request, -1); context.setResponse(response); httpProcessor.process(response, entityDetails, context); connMetrics.incrementResponseCount(); exchangeHandler.consumePromise(request, response, entityDetails, context); if (endStream) { responseState = MessageState.COMPLETE; exchangeHandler.streamEnd(null); } else { responseState = MessageState.BODY; } } else { throw new ProtocolException("Unexpected message headers"); } } @Override public void updateInputCapacity() throws IOException { Asserts.notNull(exchangeHandler, "Exchange handler"); exchangeHandler.updateCapacity(internalOutputChannel); } @Override public void consumeData(final ByteBuffer src, final boolean endStream) throws HttpException, IOException { if (responseState != MessageState.BODY) { throw new ProtocolException("Unexpected message data"); } Asserts.notNull(exchangeHandler, "Exchange handler"); if (src != null) { exchangeHandler.consume(src); } if (endStream) { responseState = MessageState.COMPLETE; exchangeHandler.streamEnd(null); } } public boolean isDone() { return responseState == MessageState.COMPLETE; } @Override public void failed(final Exception cause) { try { if (failed.compareAndSet(false, true)) { if (exchangeHandler != null) { exchangeHandler.failed(cause); } } } finally { releaseResources(); } } @Override public void handle(final HttpException ex, final boolean endStream) throws HttpException { throw ex; } @Override public void releaseResources() { if (done.compareAndSet(false, true)) { responseState = MessageState.COMPLETE; requestState = MessageState.COMPLETE; if (exchangeHandler != null) { exchangeHandler.releaseResources(); } } } @Override public String toString() { return "[" + "requestState=" + requestState + ", responseState=" + responseState + ']'; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/FrameInputBuffer.java0100644 0000000 0000000 00000020170 00000250600 026720 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import org.apache.hc.core5.http2.H2ConnectionException; import org.apache.hc.core5.http2.H2CorruptFrameException; import org.apache.hc.core5.http2.H2Error; import org.apache.hc.core5.http2.H2TransportMetrics; import org.apache.hc.core5.http2.frame.FrameConsts; import org.apache.hc.core5.http2.frame.FrameFlag; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.impl.BasicH2TransportMetrics; import org.apache.hc.core5.util.Args; /** * Frame input buffer for HTTP/2 non-blocking connections. * * @since 5.0 */ public final class FrameInputBuffer { enum State { HEAD_EXPECTED, PAYLOAD_EXPECTED } private final BasicH2TransportMetrics metrics; private final int maxFramePayloadSize; private final byte[] bytes; private final ByteBuffer buffer; private State state; private int payloadLen; private int type; private int flags; private int streamId; private boolean endOfStream; FrameInputBuffer(final BasicH2TransportMetrics metrics, final int bufferLen, final int maxFramePayloadSize) { Args.notNull(metrics, "HTTP2 transport metrics"); Args.positive(maxFramePayloadSize, "Maximum payload size"); this.metrics = metrics; this.maxFramePayloadSize = Math.max(maxFramePayloadSize, FrameConsts.MIN_FRAME_SIZE); this.bytes = new byte[bufferLen]; this.buffer = ByteBuffer.wrap(bytes); this.buffer.flip(); this.state = State.HEAD_EXPECTED; this.endOfStream = false; } public FrameInputBuffer(final BasicH2TransportMetrics metrics, final int maxFramePayloadSize) { this(metrics, FrameConsts.HEAD_LEN + maxFramePayloadSize, maxFramePayloadSize); } public FrameInputBuffer(final int maxFramePayloadSize) { this(new BasicH2TransportMetrics(), maxFramePayloadSize); } /** * @deprecated Use {@link #read(ByteBuffer, ReadableByteChannel)}. */ @Deprecated public void put(final ByteBuffer src) { if (buffer.hasRemaining()) { buffer.compact(); } else { buffer.clear(); } buffer.put(src); buffer.flip(); } /** * Attempts to read a complete frame from the given source buffer and the underlying data * channel. The source buffer is consumed first. More data can be read from the channel * if required. * * @param src the source buffer or {@code null} if not available. * @param channel the underlying data channel. * @return a complete frame or {@code null} a complete frame cannot be read. * @throws IOException in case of an I/O error. * @since 5.1 */ public RawFrame read(final ByteBuffer src, final ReadableByteChannel channel) throws IOException { for (;;) { if (src != null) { if (buffer.hasRemaining()) { buffer.compact(); } else { buffer.clear(); } final int remaining = buffer.remaining(); final int n = src.remaining(); if (remaining >= n) { buffer.put(src); metrics.incrementBytesTransferred(n); } else { final int limit = src.limit(); src.limit(remaining); buffer.put(src); src.limit(limit); metrics.incrementBytesTransferred(remaining); } buffer.flip(); } switch (state) { case HEAD_EXPECTED: if (buffer.remaining() >= FrameConsts.HEAD_LEN) { final int lengthAndType = buffer.getInt(); payloadLen = lengthAndType >> 8; if (payloadLen > maxFramePayloadSize) { throw new H2ConnectionException(H2Error.FRAME_SIZE_ERROR, "Frame size exceeds maximum"); } type = lengthAndType & 0xff; flags = buffer.get(); streamId = Math.abs(buffer.getInt()); state = State.PAYLOAD_EXPECTED; } else { break; } case PAYLOAD_EXPECTED: if (buffer.remaining() >= payloadLen) { if ((flags & FrameFlag.PADDED.getValue()) > 0) { if (payloadLen == 0) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Inconsistent padding"); } buffer.mark(); final int padding = buffer.get(); if (payloadLen < padding + 1) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Inconsistent padding"); } buffer.reset(); } final ByteBuffer payload = payloadLen > 0 ? ByteBuffer.wrap(bytes, buffer.position(), payloadLen) : null; buffer.position(buffer.position() + payloadLen); state = State.HEAD_EXPECTED; metrics.incrementFramesTransferred(); return new RawFrame(type, flags, streamId, payload); } } if (buffer.hasRemaining()) { buffer.compact(); } else { buffer.clear(); } final int bytesRead = channel.read(buffer); buffer.flip(); if (bytesRead > 0) { metrics.incrementBytesTransferred(bytesRead); } if (bytesRead == 0) { break; } if (bytesRead == -1) { if (state != State.HEAD_EXPECTED || buffer.hasRemaining()) { throw new H2CorruptFrameException("Corrupt or incomplete HTTP2 frame"); } else { endOfStream = true; break; } } } return null; } /** * Attempts to read a complete frame from the underlying data channel. * * @param channel the underlying data channel. * @return a complete frame or {@code null} a complete frame cannot be read. * @throws IOException in case of an I/O error. */ public RawFrame read(final ReadableByteChannel channel) throws IOException { return read(null, channel); } public void reset() { buffer.compact(); state = State.HEAD_EXPECTED; endOfStream = false; } public H2TransportMetrics getMetrics() { return metrics; } /** * @since 5.4 */ public boolean isEndOfStream() { return endOfStream; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/FrameOutputBuffer.java0100644 0000000 0000000 00000011325 00000250600 027123 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.GatheringByteChannel; import java.nio.channels.WritableByteChannel; import org.apache.hc.core5.http2.H2TransportMetrics; import org.apache.hc.core5.http2.frame.FrameConsts; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.impl.BasicH2TransportMetrics; import org.apache.hc.core5.util.Args; /** * Frame output buffer for HTTP/2 non-blocking connections. * * @since 5.0 */ public final class FrameOutputBuffer { private final BasicH2TransportMetrics metrics; private int maxFramePayloadSize; private ByteBuffer buffer; public FrameOutputBuffer(final BasicH2TransportMetrics metrics, final int maxFramePayloadSize) { Args.notNull(metrics, "HTTP2 transport metrics"); Args.positive(maxFramePayloadSize, "Maximum payload size"); this.metrics = metrics; this.maxFramePayloadSize = maxFramePayloadSize; this.buffer = ByteBuffer.allocate(FrameConsts.HEAD_LEN + maxFramePayloadSize); } public FrameOutputBuffer(final int maxFramePayloadSize) { this(new BasicH2TransportMetrics(), maxFramePayloadSize); } /** * @deprecated Misnomer. Use {@link #resize(int)}. */ @Deprecated public void expand(final int maxFramePayloadSize) { resize(maxFramePayloadSize); } /** * @since 5.2 */ public int getMaxFramePayloadSize() { return maxFramePayloadSize; } /** * @since 5.2 */ public void resize(final int maxFramePayloadSize) { if (buffer.capacity() == maxFramePayloadSize) { return; } this.maxFramePayloadSize = maxFramePayloadSize; final ByteBuffer newBuffer = ByteBuffer.allocate(FrameConsts.HEAD_LEN + maxFramePayloadSize); if (buffer.position() > 0) { buffer.flip(); newBuffer.put(buffer); } buffer = newBuffer; } public void write(final RawFrame frame, final WritableByteChannel channel) throws IOException { Args.notNull(frame, "Frame"); final ByteBuffer payload = frame.getPayload(); Args.check(payload == null || payload.remaining() <= maxFramePayloadSize, "Frame size exceeds maximum"); buffer.putInt((payload != null ? payload.remaining() << 8 : 0) | (frame.getType() & 0xff)); buffer.put((byte) (frame.getFlags() & 0xff)); buffer.putInt(frame.getStreamId()); if (payload != null) { if (channel instanceof GatheringByteChannel) { buffer.flip(); ((GatheringByteChannel) channel).write(new ByteBuffer[]{buffer, payload}); buffer.compact(); if (payload.hasRemaining()) { buffer.put(payload); } } else { buffer.put(payload); } } flush(channel); metrics.incrementFramesTransferred(); } public void flush(final WritableByteChannel channel) throws IOException { if (buffer.position() > 0) { buffer.flip(); try { final int bytesWritten = channel.write(buffer); if (bytesWritten > 0) { metrics.incrementBytesTransferred(bytesWritten); } } finally { buffer.compact(); } } } public boolean isEmpty() { return buffer.position() == 0; } public H2TransportMetrics getMetrics() { return metrics; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2Stream.java0100644 0000000 0000000 00000023241 00000250600 025143 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.CancelledKeyException; import java.nio.charset.CharacterCodingException; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http2.H2Error; import org.apache.hc.core5.http2.H2StreamResetException; import org.apache.hc.core5.http2.priority.PriorityValue; import org.apache.hc.core5.util.Timeout; class H2Stream { enum State { RESERVED, OPEN, CLOSED } private static final long LINGER_TIME = 1000; // 1 second private final H2StreamChannel channel; private final H2StreamHandler handler; private final Consumer stateChangeCallback; private final AtomicReference transitionRef; private final AtomicBoolean released; private final AtomicBoolean cancelled; private volatile boolean reserved; private volatile boolean remoteClosed; private volatile long lastActivityNanos; private volatile Timeout idleTimeout; private volatile PriorityValue priorityValue; H2Stream(final H2StreamChannel channel, final H2StreamHandler handler, final Consumer stateChangeCallback) { this.channel = channel; this.handler = handler; this.stateChangeCallback = stateChangeCallback; this.reserved = true; this.transitionRef = new AtomicReference<>(State.RESERVED); this.released = new AtomicBoolean(); this.cancelled = new AtomicBoolean(); } int getId() { return channel.getId(); } boolean isReserved() { return reserved; } private void triggerOpen() { if (transitionRef.compareAndSet(State.RESERVED, State.OPEN) && stateChangeCallback != null) { stateChangeCallback.accept(State.OPEN); } } private void triggerClosed() { if (transitionRef.compareAndSet(State.OPEN, State.CLOSED) && stateChangeCallback != null) { stateChangeCallback.accept(State.CLOSED); } } void activate() { reserved = false; triggerOpen(); } AtomicInteger getOutputWindow() { return channel.getOutputWindow(); } AtomicInteger getInputWindow() { return channel.getInputWindow(); } private boolean isPastLingerDeadline() { final long localResetTime = channel.getLocalResetTime(); return localResetTime > 0 && localResetTime + LINGER_TIME < System.currentTimeMillis(); } boolean isClosedPastLingerDeadline() { return channel.isLocalClosed() && (remoteClosed || isPastLingerDeadline()); } boolean isClosed() { return channel.isLocalClosed() && (remoteClosed || channel.isLocalReset()); } boolean isActive() { return !reserved && !isClosed(); } boolean isRemoteClosed() { return remoteClosed; } void markRemoteClosed() { remoteClosed = true; } boolean isLocalClosed() { return channel.isLocalClosed(); } void consumePromise(final List
headers) throws HttpException, IOException { try { if (channel.isLocalReset()) { return; } if (cancelled.get()) { localResetCancelled(); return; } handler.consumePromise(headers); channel.markLocalClosed(); } catch (final ProtocolException ex) { localReset(ex, H2Error.PROTOCOL_ERROR); } } void consumeHeader(final List
headers, final boolean endOfStream) throws HttpException, IOException { try { if (endOfStream) { remoteClosed = true; } if (channel.isLocalReset()) { return; } if (cancelled.get()) { localResetCancelled(); return; } handler.consumeHeader(headers, remoteClosed); } catch (final ProtocolException ex) { localReset(ex, H2Error.PROTOCOL_ERROR); } } void consumeData(final ByteBuffer src, final boolean endOfStream) throws HttpException, IOException { try { if (endOfStream) { remoteClosed = true; } if (channel.isLocalReset()) { return; } if (cancelled.get()) { localResetCancelled(); return; } handler.consumeData(src, remoteClosed); } catch (final CharacterCodingException ex) { localReset(ex, H2Error.INTERNAL_ERROR); } catch (final ProtocolException ex) { localReset(ex, H2Error.PROTOCOL_ERROR); } } boolean isOutputReady() { return !reserved && !channel.isLocalClosed() && handler.isOutputReady(); } void produceOutput() throws HttpException, IOException { try { handler.produceOutput(); } catch (final ProtocolException ex) { localReset(ex, H2Error.PROTOCOL_ERROR); } } void produceInputCapacityUpdate() throws IOException { handler.updateInputCapacity(); } void fail(final Exception cause) { remoteClosed = true; channel.markLocalClosed(); if (released.compareAndSet(false, true)) { try { handler.failed(cause); handler.releaseResources(); } finally { triggerClosed(); } } } void localReset(final Exception cause, final int code) throws IOException { channel.localReset(code); if (released.compareAndSet(false, true)) { try { handler.failed(cause); handler.releaseResources(); } finally { triggerClosed(); } } } void localReset(final Exception cause, final H2Error error) throws IOException { localReset(cause, error != null ? error.getCode() : H2Error.INTERNAL_ERROR.getCode()); } void localReset(final H2StreamResetException ex) throws IOException { localReset(ex, ex.getCode()); } void localResetCancelled() throws IOException { localReset(new H2StreamResetException(H2Error.CANCEL, "Cancelled")); } void handle(final HttpException ex) throws IOException, HttpException { handler.handle(ex, remoteClosed); } HandlerFactory getPushHandlerFactory() { return handler.getPushHandlerFactory(); } boolean abort() { if (cancelled.compareAndSet(false, true)) { try { channel.requestOutput(); return true; } catch (final CancelledKeyException ignore) { } } return false; } boolean abortGracefully() throws IOException { if (!isLocalClosed() && isRemoteClosed()) { channel.endStream(); releaseResources(); return true; } else { return abort(); } } void releaseResources() { if (released.compareAndSet(false, true)) { try { handler.releaseResources(); } finally { triggerClosed(); } } } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append("[") .append("id=").append(channel.getId()) .append(", reserved=").append(reserved) .append(", remoteClosed=").append(remoteClosed) .append(", localClosed=").append(channel.isLocalClosed()) .append(", localReset=").append(channel.isLocalReset()) .append("]"); return buf.toString(); } private void touch() { this.lastActivityNanos = System.nanoTime(); } long getLastActivityNanos() { return lastActivityNanos; } Timeout getIdleTimeout() { return idleTimeout; } PriorityValue getPriorityValue() { return priorityValue; } void setPriorityValue(final PriorityValue priorityValue) { this.priorityValue = priorityValue; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2StreamChannel.java0100644 0000000 0000000 00000005062 00000250600 026435 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.apache.hc.core5.concurrent.Cancellable; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.nio.AsyncPushProducer; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http2.H2Error; interface H2StreamChannel extends DataStreamChannel, CapacityChannel, Cancellable { int getId(); AtomicInteger getOutputWindow(); AtomicInteger getInputWindow(); void submit(List
headers, boolean endStream) throws HttpException, IOException; void push(List
headers, AsyncPushProducer pushProducer) throws HttpException, IOException; boolean isLocalClosed(); void markLocalClosed(); boolean localReset(int errorCode) throws IOException; default boolean localReset(H2Error error) throws IOException { return localReset(error != null ? error.getCode() : H2Error.INTERNAL_ERROR.getCode()); } default void terminate() { try { localReset(H2Error.INTERNAL_ERROR); } catch (final IOException ignore) { } } long getLocalResetTime(); default boolean isLocalReset() { return getLocalResetTime() > 0; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2StreamHandler.java0100644 0000000 0000000 00000004263 00000250600 026444 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.nio.ResourceHolder; interface H2StreamHandler extends ResourceHolder { boolean isOutputReady(); void produceOutput() throws HttpException, IOException; void consumePromise(List
headers) throws HttpException, IOException; void consumeHeader(List
headers, boolean endStream) throws HttpException, IOException; void updateInputCapacity() throws IOException; void consumeData(ByteBuffer src, boolean endStream) throws HttpException, IOException; HandlerFactory getPushHandlerFactory(); void failed(Exception cause); void handle(final HttpException ex, final boolean endStream) throws HttpException, IOException; } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2StreamListener.java0100644 0000000 0000000 00000004323 00000250600 026651 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.util.List; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http2.frame.RawFrame; /** * HTTP/2 stream event listener. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.STATELESS) @Internal public interface H2StreamListener { void onHeaderInput(HttpConnection connection, int streamId, List headers); void onHeaderOutput(HttpConnection connection, int streamId, List headers); void onFrameInput(HttpConnection connection, int streamId, RawFrame frame); void onFrameOutput(HttpConnection connection, int streamId, RawFrame frame); void onInputFlowControl(HttpConnection connection, int streamId, int delta, int actualSize); void onOutputFlowControl(HttpConnection connection, int streamId, int delta, int actualSize); } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2Streams.java0100644 0000000 0000000 00000016120 00000250600 025324 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.util.Iterator; import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http2.H2ConnectionException; import org.apache.hc.core5.http2.H2Error; import org.apache.hc.core5.http2.H2StreamResetException; import org.apache.hc.core5.http2.frame.StreamIdGenerator; import org.apache.hc.core5.util.Args; class H2Streams { private final StreamIdGenerator idGenerator; private final Map streamMap; private final Queue streams; private final AtomicInteger lastLocalId; private final AtomicInteger lastRemoteId; private final AtomicInteger localCount; private final AtomicInteger remoteCount; public H2Streams(final StreamIdGenerator idGenerator) { this.idGenerator = Args.notNull(idGenerator, "Stream id generator"); this.streamMap = new ConcurrentHashMap<>(); this.streams = new ConcurrentLinkedQueue<>(); this.lastLocalId = new AtomicInteger(0); this.lastRemoteId = new AtomicInteger(0); this.localCount = new AtomicInteger(0); this.remoteCount = new AtomicInteger(0); } public boolean isEmpty() { return streams.isEmpty(); } public Iterator iterator() { return streams.iterator(); } public int getLastLocalId() { return lastLocalId.get(); } public int getLastRemoteId() { return lastRemoteId.get(); } public int getLocalCount() { return localCount.get(); } public int getRemoteCount() { return remoteCount.get(); } private H2Stream createStream(final H2StreamChannel channel, final H2StreamHandler streamHandler) { final int streamId = channel.getId(); final boolean remoteStream = isOtherSide(streamId); final H2Stream stream = new H2Stream(channel, streamHandler, state -> { final AtomicInteger count = remoteStream ? remoteCount : localCount; switch (state) { case OPEN: count.incrementAndGet(); break; case CLOSED: count.decrementAndGet(); } }); if (remoteStream) { final int currentId = lastRemoteId.get(); if (streamId > currentId) { lastRemoteId.compareAndSet(currentId, streamId); } } streamMap.put(streamId, stream); streams.add(stream); return stream; } public H2Stream createActive(final H2StreamChannel channel, final H2StreamHandler streamHandler) { final H2Stream stream = createStream(channel, streamHandler); if (!stream.isClosed()) { stream.activate(); } return stream; } public H2Stream createReserved(final H2StreamChannel channel, final H2StreamHandler streamHandler) { return createStream(channel, streamHandler); } public void resetIfExceedsMaxConcurrentLimit(final H2Stream stream, final int max) throws IOException { if (stream.isActive() && getRemoteCount() > max) { stream.localReset(new H2StreamResetException(H2Error.REFUSED_STREAM, "Local SETTINGS_MAX_CONCURRENT_STREAMS exceeded")); } } public void dropStreamId(final int streamId) { streamMap.remove(streamId); } public void shutdownAndReleaseAll(final Exception cause) { for (final Iterator it = streams.iterator(); it.hasNext(); ) { final H2Stream stream = it.next(); if (stream.isClosed()) { stream.releaseResources(); } else { stream.fail(cause); } } streams.clear(); streamMap.clear(); } public void shutdownAndReleaseAll() { shutdownAndReleaseAll(new ConnectionClosedException()); } public H2Stream lookup(final int streamId) { return streamMap.get(streamId); } boolean hasBeenSeen(final int streamId) { return streamId <= (isSameSide(streamId) ? lastLocalId : lastRemoteId).get(); } boolean isClosed(final H2Stream stream, final int streamId) { return stream != null ? stream.isLocalClosed() && stream.isRemoteClosed() : hasBeenSeen(streamId); } public H2Stream lookupValidOrNull(final int streamId) throws H2ConnectionException { final H2Stream stream = streamMap.get(streamId); if (isClosed(stream, streamId)) { throw new H2ConnectionException(H2Error.STREAM_CLOSED, "Stream closed"); } return stream; } public H2Stream lookupSeen(final int streamId) throws H2ConnectionException { final H2Stream stream = streamMap.get(streamId); if (stream == null && !hasBeenSeen(streamId)) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Unexpected stream id: " + streamId); } return stream; } public H2Stream lookupValid(final int streamId) throws H2ConnectionException { final H2Stream stream = lookupValidOrNull(streamId); if (stream == null) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Unexpected stream id: " + streamId); } return stream; } public boolean isSameSide(final int streamId) { return idGenerator.isSameSide(streamId); } public boolean isOtherSide(final int streamId) { return !idGenerator.isSameSide(streamId); } public int generateStreamId() { for (;;) { final int currentId = lastLocalId.get(); final int newStreamId = idGenerator.generate(currentId); if (lastLocalId.compareAndSet(currentId, newStreamId)) { return newStreamId; } } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/HttpProtocolNegotiator.java0100644 0000000 0000000 00000015137 00000250600 030220 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLSession; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.EndpointDetails; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.impl.nio.HttpConnectionEventHandler; import org.apache.hc.core5.http.nio.command.CommandSupport; import org.apache.hc.core5.http2.ssl.ApplicationProtocol; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.SocketTimeoutExceptionFactory; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.reactor.ssl.TlsDetails; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.util.Timeout; /** * @since 5.2 */ @Internal public class HttpProtocolNegotiator implements HttpConnectionEventHandler { private final ProtocolIOSession ioSession; private final FutureCallback resultCallback; private final AtomicBoolean completed; private final AtomicReference negotiatedProtocolRef; public HttpProtocolNegotiator( final ProtocolIOSession ioSession, final FutureCallback resultCallback) { this.ioSession = Args.notNull(ioSession, "I/O session"); this.resultCallback = resultCallback; this.completed = new AtomicBoolean(); this.negotiatedProtocolRef = new AtomicReference<>(); } void startProtocol(final HttpVersion httpVersion) { ioSession.switchProtocol( httpVersion == HttpVersion.HTTP_2 ? ApplicationProtocol.HTTP_2.id : ApplicationProtocol.HTTP_1_1.id, resultCallback); negotiatedProtocolRef.set(httpVersion); } @Override public void connected(final IOSession session) throws IOException { final HttpVersion httpVersion; final TlsDetails tlsDetails = ioSession.getTlsDetails(); if (tlsDetails != null) { final String appProtocol = tlsDetails.getApplicationProtocol(); if (TextUtils.isEmpty(appProtocol)) { httpVersion = HttpVersion.HTTP_1_1; } else if (appProtocol.equals(ApplicationProtocol.HTTP_1_1.id)) { httpVersion = HttpVersion.HTTP_1_1; } else if (appProtocol.equals(ApplicationProtocol.HTTP_2.id)) { httpVersion = HttpVersion.HTTP_2; } else { throw new ProtocolNegotiationException("Unsupported application protocol: " + appProtocol); } } else { httpVersion = HttpVersion.HTTP_1_1; } startProtocol(httpVersion); } @Override public void inputReady(final IOSession session, final ByteBuffer src) throws IOException { throw new ProtocolNegotiationException("Unexpected input"); } @Override public void outputReady(final IOSession session) throws IOException { throw new ProtocolNegotiationException("Unexpected output"); } @Override public void timeout(final IOSession session, final Timeout timeout) { exception(session, SocketTimeoutExceptionFactory.create(timeout)); } @Override public void exception(final IOSession session, final Exception cause) { try { session.close(CloseMode.IMMEDIATE); CommandSupport.failCommands(session, cause); } catch (final Exception ex) { if (completed.compareAndSet(false, true) && resultCallback != null) { resultCallback.failed(ex); } } } @Override public void disconnected(final IOSession session) { try { CommandSupport.cancelCommands(session); } finally { if (completed.compareAndSet(false, true) && resultCallback != null) { resultCallback.failed(new ConnectionClosedException()); } } } @Override public SSLSession getSSLSession() { final TlsDetails tlsDetails = ioSession.getTlsDetails(); return tlsDetails != null ? tlsDetails.getSSLSession() : null; } @Override public EndpointDetails getEndpointDetails() { return null; } @Override public void setSocketTimeout(final Timeout timeout) { ioSession.setSocketTimeout(timeout); } @Override public Timeout getSocketTimeout() { return ioSession.getSocketTimeout(); } @Override public ProtocolVersion getProtocolVersion() { return negotiatedProtocolRef.get(); } @Override public SocketAddress getRemoteAddress() { return ioSession.getRemoteAddress(); } @Override public SocketAddress getLocalAddress() { return ioSession.getLocalAddress(); } @Override public boolean isOpen() { return ioSession.isOpen(); } @Override public void close() throws IOException { ioSession.close(); } @Override public void close(final CloseMode closeMode) { ioSession.close(closeMode); } @Override public String toString() { return getClass().getName(); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/NoopAsyncPushHandler.java0100644 0000000 0000000 00000004775 00000250600 027600 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.protocol.HttpContext; class NoopAsyncPushHandler implements AsyncPushConsumer { @Override public void consumePromise(final HttpRequest promise, final HttpResponse response, final EntityDetails entityDetails, final HttpContext context) throws HttpException, IOException { } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { capacityChannel.update(Integer.MAX_VALUE); } @Override public void consume(final ByteBuffer src) throws IOException { } @Override public void streamEnd(final List trailers) throws HttpException, IOException { } @Override public void failed(final Exception cause) { } @Override public void releaseResources() { } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/NoopH2StreamHandler.java0100644 0000000 0000000 00000005132 00000250600 027274 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.HandlerFactory; final class NoopH2StreamHandler implements H2StreamHandler { static final NoopH2StreamHandler INSTANCE = new NoopH2StreamHandler(); @Override public boolean isOutputReady() { return false; } @Override public void produceOutput() throws HttpException, IOException { } @Override public void consumePromise(final List
headers) throws HttpException, IOException { } @Override public void consumeHeader(final List
headers, final boolean endStream) throws HttpException, IOException { } @Override public void updateInputCapacity() throws IOException { } @Override public void consumeData(final ByteBuffer src, final boolean endStream) throws HttpException, IOException { } @Override public HandlerFactory getPushHandlerFactory() { return null; } @Override public void failed(final Exception cause) { } @Override public void handle(final HttpException ex, final boolean endStream) throws HttpException, IOException { } @Override public void releaseResources() { } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/PrefaceHandlerBase.java0100644 0000000 0000000 00000014062 00000250600 027155 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLSession; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.EndpointDetails; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.impl.nio.HttpConnectionEventHandler; import org.apache.hc.core5.http.nio.command.CommandSupport; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.SocketTimeoutExceptionFactory; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.reactor.ssl.TlsDetails; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; abstract class PrefaceHandlerBase implements HttpConnectionEventHandler { final ProtocolIOSession ioSession; private final AtomicReference protocolHandlerRef; private final FutureCallback resultCallback; private final Callback exceptionCallback; private final AtomicBoolean completed; PrefaceHandlerBase( final ProtocolIOSession ioSession, final FutureCallback resultCallback, final Callback exceptionCallback) { this.ioSession = Args.notNull(ioSession, "I/O session"); this.protocolHandlerRef = new AtomicReference<>(); this.resultCallback = resultCallback; this.exceptionCallback = exceptionCallback; this.completed = new AtomicBoolean(); } void startProtocol(final HttpConnectionEventHandler protocolHandler, final ByteBuffer data) throws IOException { protocolHandlerRef.set(protocolHandler); ioSession.upgrade(protocolHandler); protocolHandler.connected(ioSession); if (data != null && data.hasRemaining()) { protocolHandler.inputReady(ioSession, data); } if (completed.compareAndSet(false, true) && resultCallback != null) { resultCallback.completed(ioSession); } } @Override public void timeout(final IOSession session, final Timeout timeout) { exception(session, SocketTimeoutExceptionFactory.create(timeout)); } @Override public void exception(final IOSession session, final Exception cause) { final HttpConnectionEventHandler protocolHandler = protocolHandlerRef.get(); try { session.close(CloseMode.IMMEDIATE); if (protocolHandler != null) { protocolHandler.exception(session, cause); } else { CommandSupport.failCommands(session, cause); } } catch (final Exception ex) { if (completed.compareAndSet(false, true) && resultCallback != null) { resultCallback.failed(ex); } if (exceptionCallback != null) { exceptionCallback.execute(cause); } } } @Override public void disconnected(final IOSession session) { final HttpConnectionEventHandler protocolHandler = protocolHandlerRef.getAndSet(null); try { if (protocolHandler != null) { protocolHandler.disconnected(ioSession); } else { CommandSupport.cancelCommands(session); } } finally { if (completed.compareAndSet(false, true) && resultCallback != null) { resultCallback.failed(new ConnectionClosedException()); } } } @Override public SSLSession getSSLSession() { final TlsDetails tlsDetails = ioSession.getTlsDetails(); return tlsDetails != null ? tlsDetails.getSSLSession() : null; } @Override public EndpointDetails getEndpointDetails() { return null; } @Override public void setSocketTimeout(final Timeout timeout) { ioSession.setSocketTimeout(timeout); } @Override public Timeout getSocketTimeout() { return ioSession.getSocketTimeout(); } @Override public ProtocolVersion getProtocolVersion() { return HttpVersion.HTTP_2; } @Override public SocketAddress getRemoteAddress() { return ioSession.getRemoteAddress(); } @Override public SocketAddress getLocalAddress() { return ioSession.getLocalAddress(); } @Override public boolean isOpen() { return ioSession.isOpen(); } @Override public void close() throws IOException { ioSession.close(); } @Override public void close(final CloseMode closeMode) { ioSession.close(closeMode); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ProtocolNegotiationException.java0100644 0000000 0000000 00000003445 00000250600 031403 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; /** * Signals a protocol error in HTTP protocol negotiation. * * @since 5.1 */ public class ProtocolNegotiationException extends IOException { /** * Required for serialization support. * * @see java.io.Serializable */ private static final long serialVersionUID = 6211774735704945037L; /** * Creates a MessageConstraintException with the specified detail message. * * @param message The exception detail message */ public ProtocolNegotiationException(final String message) { super(message); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2IOEventHandler.java0100644 0000000 0000000 00000004021 00000250600 027701 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import org.apache.hc.core5.net.InetAddressUtils; /** * {@link org.apache.hc.core5.reactor.IOEventHandler} that implements * server side HTTP/2 messaging protocol with full support for * multiplexed message transmission. * * @since 5.0 */ public class ServerH2IOEventHandler extends AbstractH2IOEventHandler { public ServerH2IOEventHandler(final ServerH2StreamMultiplexer streamMultiplexer) { super(streamMultiplexer); } @Override public String toString() { final StringBuilder buf = new StringBuilder(); InetAddressUtils.formatAddress(buf, getRemoteAddress()); buf.append("->"); InetAddressUtils.formatAddress(buf, getLocalAddress()); buf.append(" ["); streamMultiplexer.appendState(buf); buf.append("]"); return buf.toString(); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2PrefaceHandler.java0100644 0000000 0000000 00000010174 00000250600 027743 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.impl.nio.BufferedData; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.util.Args; /** * I/O event handler for events fired by {@link ProtocolIOSession} that implements * server side of the HTTP/2 protocol negotiation handshake. * * @since 5.2 */ @Internal public class ServerH2PrefaceHandler extends PrefaceHandlerBase { final static byte[] PREFACE = ClientH2PrefaceHandler.PREFACE; private final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory; private final BufferedData inBuf; public ServerH2PrefaceHandler( final ProtocolIOSession ioSession, final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory, final Callback exceptionCallback) { this(ioSession, http2StreamHandlerFactory, null, exceptionCallback); } public ServerH2PrefaceHandler( final ProtocolIOSession ioSession, final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory, final FutureCallback resultCallback, final Callback exceptionCallback) { super(ioSession, resultCallback, exceptionCallback); this.http2StreamHandlerFactory = Args.notNull(http2StreamHandlerFactory, "HTTP/2 stream handler factory"); this.inBuf = BufferedData.allocate(1024); } @Override public void connected(final IOSession session) throws IOException { } @Override public void inputReady(final IOSession session, final ByteBuffer src) throws IOException { if (src != null) { inBuf.put(src); } boolean endOfStream = false; if (inBuf.length() < PREFACE.length) { final int bytesRead = inBuf.readFrom(session); if (bytesRead == -1) { endOfStream = true; } } final ByteBuffer data = inBuf.data(); if (data.remaining() >= PREFACE.length) { for (int i = 0; i < PREFACE.length; i++) { if (data.get() != PREFACE[i]) { throw new ProtocolNegotiationException("Unexpected HTTP/2 preface"); } } startProtocol(new ServerH2IOEventHandler(http2StreamHandlerFactory.create(ioSession)), data.hasRemaining() ? data : null); } else { if (endOfStream) { throw new ConnectionClosedException(); } } } @Override public void outputReady(final IOSession session) throws IOException { } @Override public String toString() { return getClass().getName(); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamHandler.java0100644 0000000 0000000 00000035303 00000250600 027632 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.impl.BasicHttpConnectionMetrics; import org.apache.hc.core5.http.impl.IncomingEntityDetails; import org.apache.hc.core5.http.impl.ServerSupport; import org.apache.hc.core5.http.impl.nio.MessageState; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.AsyncPushProducer; import org.apache.hc.core5.http.nio.AsyncResponseProducer; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.nio.support.BasicResponseProducer; import org.apache.hc.core5.http.nio.support.ImmediateResponseExchangeHandler; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http2.H2ConnectionException; import org.apache.hc.core5.http2.H2Error; import org.apache.hc.core5.http2.H2StreamResetException; import org.apache.hc.core5.http2.impl.DefaultH2RequestConverter; import org.apache.hc.core5.http2.impl.DefaultH2ResponseConverter; import org.apache.hc.core5.util.Asserts; class ServerH2StreamHandler implements H2StreamHandler { private final H2StreamChannel outputChannel; private final DataStreamChannel dataChannel; private final ResponseChannel responseChannel; private final HttpProcessor httpProcessor; private final BasicHttpConnectionMetrics connMetrics; private final HandlerFactory exchangeHandlerFactory; private final HttpCoreContext context; private final AtomicReference requestState; private final AtomicReference responseState; private final AtomicBoolean responseCommitted; private final AtomicBoolean failed; private final AtomicBoolean done; private volatile AsyncServerExchangeHandler exchangeHandler; private volatile HttpRequest receivedRequest; ServerH2StreamHandler( final H2StreamChannel outputChannel, final HttpProcessor httpProcessor, final BasicHttpConnectionMetrics connMetrics, final HandlerFactory exchangeHandlerFactory, final HttpCoreContext context) { this.outputChannel = outputChannel; this.dataChannel = new DataStreamChannel() { @Override public void requestOutput() { outputChannel.requestOutput(); } @Override public int write(final ByteBuffer src) throws IOException { return outputChannel.write(src); } @Override public void endStream(final List trailers) throws IOException { outputChannel.endStream(trailers); responseState.set(MessageState.COMPLETE); } @Override public void endStream() throws IOException { outputChannel.endStream(); responseState.set(MessageState.COMPLETE); } }; this.responseChannel = new ResponseChannel() { @Override public void sendInformation(final HttpResponse response, final HttpContext httpContext) throws HttpException, IOException { commitInformation(response); } @Override public void sendResponse( final HttpResponse response, final EntityDetails responseEntityDetails, final HttpContext httpContext) throws HttpException, IOException { commitResponse(response, responseEntityDetails); } @Override public void pushPromise( final HttpRequest promise, final AsyncPushProducer pushProducer, final HttpContext httpContext) throws HttpException, IOException { commitPromise(promise, pushProducer); } @Override public void terminateExchange() { terminate(); } }; this.httpProcessor = httpProcessor; this.connMetrics = connMetrics; this.exchangeHandlerFactory = exchangeHandlerFactory; this.context = context; this.responseCommitted = new AtomicBoolean(); this.failed = new AtomicBoolean(); this.done = new AtomicBoolean(); this.requestState = new AtomicReference<>(MessageState.HEADERS); this.responseState = new AtomicReference<>(MessageState.IDLE); } @Override public HandlerFactory getPushHandlerFactory() { return null; } private void commitInformation(final HttpResponse response) throws IOException, HttpException { if (responseCommitted.get()) { throw new H2ConnectionException(H2Error.INTERNAL_ERROR, "Response already committed"); } final int status = response.getCode(); if (status < HttpStatus.SC_INFORMATIONAL || status >= HttpStatus.SC_SUCCESS) { throw new HttpException("Invalid intermediate response: " + status); } final List
responseHeaders = DefaultH2ResponseConverter.INSTANCE.convert(response); outputChannel.submit(responseHeaders, false); } private void commitResponse( final HttpResponse response, final EntityDetails responseEntityDetails) throws HttpException, IOException { if (responseCommitted.compareAndSet(false, true)) { final int status = response.getCode(); if (status < HttpStatus.SC_SUCCESS) { throw new HttpException("Invalid response: " + status); } context.setResponse(response); httpProcessor.process(response, responseEntityDetails, context); final List
responseHeaders = DefaultH2ResponseConverter.INSTANCE.convert(response); final boolean endStream = responseEntityDetails == null || receivedRequest != null && Method.HEAD.isSame(receivedRequest.getMethod()); if (endStream) { responseState.set(MessageState.COMPLETE); outputChannel.submit(responseHeaders, endStream); connMetrics.incrementResponseCount(); } else { outputChannel.submit(responseHeaders, endStream); connMetrics.incrementResponseCount(); exchangeHandler.produce(outputChannel); if (responseState.compareAndSet(MessageState.IDLE, MessageState.BODY)) { outputChannel.requestOutput(); } } } else { throw new H2ConnectionException(H2Error.INTERNAL_ERROR, "Response already committed"); } } private void commitPromise( final HttpRequest promise, final AsyncPushProducer pushProducer) throws HttpException, IOException { httpProcessor.process(promise, null, context); final List
promiseHeaders = DefaultH2RequestConverter.INSTANCE.convert(promise); outputChannel.push(promiseHeaders, pushProducer); connMetrics.incrementRequestCount(); } private void terminate() { outputChannel.terminate(); } @Override public void consumePromise(final List
headers) throws HttpException, IOException { throw new ProtocolException("Unexpected message promise"); } @Override public void consumeHeader(final List
headers, final boolean endStream) throws HttpException, IOException { if (done.get()) { throw new ProtocolException("Unexpected message headers"); } switch (requestState.get()) { case HEADERS: requestState.set(endStream ? MessageState.COMPLETE : MessageState.BODY); final HttpRequest request = DefaultH2RequestConverter.INSTANCE.convert(headers); final EntityDetails requestEntityDetails = endStream ? null : new IncomingEntityDetails(request, -1); final AsyncServerExchangeHandler handler; try { handler = exchangeHandlerFactory != null ? exchangeHandlerFactory.create(request, context) : null; } catch (final ProtocolException ex) { throw new H2StreamResetException(H2Error.PROTOCOL_ERROR, ex.getMessage()); } if (handler == null) { throw new H2StreamResetException(H2Error.REFUSED_STREAM, "Stream refused"); } exchangeHandler = handler; context.setProtocolVersion(HttpVersion.HTTP_2); context.setRequest(request); try { httpProcessor.process(request, requestEntityDetails, context); connMetrics.incrementRequestCount(); receivedRequest = request; exchangeHandler.handleRequest(request, requestEntityDetails, responseChannel, context); } catch (final HttpException ex) { if (!responseCommitted.get()) { final AsyncResponseProducer responseProducer = new BasicResponseProducer( ServerSupport.toStatusCode(ex), ServerSupport.toErrorMessage(ex)); exchangeHandler = new ImmediateResponseExchangeHandler(responseProducer); exchangeHandler.handleRequest(request, requestEntityDetails, responseChannel, context); } else { throw ex; } } break; case BODY: responseState.set(MessageState.COMPLETE); exchangeHandler.streamEnd(headers); break; default: throw new ProtocolException("Unexpected message headers"); } } @Override public void updateInputCapacity() throws IOException { Asserts.notNull(exchangeHandler, "Exchange handler"); exchangeHandler.updateCapacity(outputChannel); } @Override public void consumeData(final ByteBuffer src, final boolean endStream) throws HttpException, IOException { if (done.get() || requestState.get() != MessageState.BODY) { throw new ProtocolException("Unexpected message data"); } Asserts.notNull(exchangeHandler, "Exchange handler"); if (src != null) { exchangeHandler.consume(src); } if (endStream) { requestState.set(MessageState.COMPLETE); exchangeHandler.streamEnd(null); } } @Override public boolean isOutputReady() { return responseState.get() == MessageState.BODY && exchangeHandler != null && exchangeHandler.available() > 0; } @Override public void produceOutput() throws HttpException, IOException { if (responseState.get() == MessageState.BODY) { Asserts.notNull(exchangeHandler, "Exchange handler"); exchangeHandler.produce(dataChannel); } } @Override public void handle(final HttpException ex, final boolean endStream) throws HttpException, IOException { if (done.get()) { throw ex; } switch (requestState.get()) { case HEADERS: requestState.set(endStream ? MessageState.COMPLETE : MessageState.BODY); if (!responseCommitted.get()) { final AsyncResponseProducer responseProducer = new BasicResponseProducer( ServerSupport.toStatusCode(ex), ServerSupport.toErrorMessage(ex)); exchangeHandler = new ImmediateResponseExchangeHandler(responseProducer); exchangeHandler.handleRequest(null, null, responseChannel, context); } else { throw ex; } break; case BODY: responseState.set(MessageState.COMPLETE); default: throw ex; } } @Override public void failed(final Exception cause) { try { if (failed.compareAndSet(false, true)) { if (exchangeHandler != null) { exchangeHandler.failed(cause); } } } finally { releaseResources(); } } @Override public void releaseResources() { if (done.compareAndSet(false, true)) { requestState.set(MessageState.COMPLETE); responseState.set(MessageState.COMPLETE); if (exchangeHandler != null) { exchangeHandler.releaseResources(); } } } @Override public String toString() { return "[" + "requestState=" + requestState.get() + ", responseState=" + responseState.get() + ']'; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamMultiplexer.java0100644 0000000 0000000 00000016124 00000250600 030567 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.RequestHeaderFieldsTooLargeException; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.AsyncPushProducer; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http2.H2ConnectionException; import org.apache.hc.core5.http2.H2Error; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.config.H2Param; import org.apache.hc.core5.http2.config.H2Setting; import org.apache.hc.core5.http2.frame.DefaultFrameFactory; import org.apache.hc.core5.http2.frame.FrameFactory; import org.apache.hc.core5.http2.frame.StreamIdGenerator; import org.apache.hc.core5.http2.hpack.HeaderListConstraintException; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.util.Args; /** * I/O event handler for events fired by {@link ProtocolIOSession} that implements * server side HTTP/2 messaging protocol with full support for * multiplexed message transmission. * * @since 5.0 */ @Internal public class ServerH2StreamMultiplexer extends AbstractH2StreamMultiplexer { private final HandlerFactory exchangeHandlerFactory; public ServerH2StreamMultiplexer( final ProtocolIOSession ioSession, final FrameFactory frameFactory, final HttpProcessor httpProcessor, final HandlerFactory exchangeHandlerFactory, final CharCodingConfig charCodingConfig, final H2Config h2Config, final H2StreamListener streamListener) { super(ioSession, frameFactory, StreamIdGenerator.EVEN, httpProcessor, charCodingConfig, h2Config, streamListener); this.exchangeHandlerFactory = Args.notNull(exchangeHandlerFactory, "Handler factory"); } public ServerH2StreamMultiplexer( final ProtocolIOSession ioSession, final HttpProcessor httpProcessor, final HandlerFactory exchangeHandlerFactory, final CharCodingConfig charCodingConfig, final H2Config h2Config) { this(ioSession, DefaultFrameFactory.INSTANCE, httpProcessor, exchangeHandlerFactory, charCodingConfig, h2Config, null); } @Override void validateSetting(final H2Param param, final int value) throws H2ConnectionException { } @Override H2Setting[] generateSettings(final H2Config localConfig) { return new H2Setting[] { new H2Setting(H2Param.HEADER_TABLE_SIZE, localConfig.getHeaderTableSize()), new H2Setting(H2Param.MAX_CONCURRENT_STREAMS, localConfig.getMaxConcurrentStreams()), new H2Setting(H2Param.INITIAL_WINDOW_SIZE, localConfig.getInitialWindowSize()), new H2Setting(H2Param.MAX_FRAME_SIZE, localConfig.getMaxFrameSize()), new H2Setting(H2Param.MAX_HEADER_LIST_SIZE, localConfig.getMaxHeaderListSize()) }; } @Override void acceptHeaderFrame() { } @Override void acceptPushRequest() { } @Override void acceptPushFrame() throws H2ConnectionException { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Push not supported"); } @Override H2StreamHandler incomingRequest(final H2StreamChannel channel) { final HttpCoreContext context = HttpCoreContext.create(); context.setSSLSession(getSSLSession()); context.setEndpointDetails(getEndpointDetails()); return new ServerH2StreamHandler(channel, getHttpProcessor(), getConnMetrics(), exchangeHandlerFactory, context); } @Override H2StreamHandler outgoingRequest( final H2StreamChannel channel, final AsyncClientExchangeHandler exchangeHandler, final HandlerFactory pushHandlerFactory, final HttpContext context) throws IOException { throw new H2ConnectionException(H2Error.INTERNAL_ERROR, "Illegal attempt to send a request"); } @Override H2StreamHandler incomingPushPromise(final H2StreamChannel channel, final HandlerFactory pushHandlerFactory) throws IOException { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal incoming push promise"); } @Override H2StreamHandler outgoingPushPromise(final H2StreamChannel channel, final AsyncPushProducer pushProducer) throws IOException { final HttpCoreContext context = HttpCoreContext.create(); context.setSSLSession(getSSLSession()); context.setEndpointDetails(getEndpointDetails()); return new ServerPushH2StreamHandler(channel, getHttpProcessor(), getConnMetrics(), pushProducer, context); } @Override boolean allowGracefulAbort(final H2Stream stream) { return false; } @Override List
decodeHeaders(final ByteBuffer payload) throws HttpException { try { return super.decodeHeaders(payload); } catch (final HeaderListConstraintException ex) { throw new RequestHeaderFieldsTooLargeException(ex.getMessage(), ex); } } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append("["); appendState(buf); buf.append("]"); return buf.toString(); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamMultiplexerFactory.java0100644 0000000 0000000 00000007713 00000250600 032123 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.frame.DefaultFrameFactory; import org.apache.hc.core5.http2.frame.FrameFactory; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.util.Args; /** * {@link ServerH2StreamMultiplexer} factory. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) @Internal public final class ServerH2StreamMultiplexerFactory { private final HttpProcessor httpProcessor; private final HandlerFactory exchangeHandlerFactory; private final H2Config h2Config; private final CharCodingConfig charCodingConfig; private final H2StreamListener streamListener; private final FrameFactory frameFactory; public ServerH2StreamMultiplexerFactory( final HttpProcessor httpProcessor, final HandlerFactory exchangeHandlerFactory, final H2Config h2Config, final CharCodingConfig charCodingConfig, final H2StreamListener streamListener, final FrameFactory frameFactory) { this.httpProcessor = Args.notNull(httpProcessor, "HTTP processor"); this.exchangeHandlerFactory = Args.notNull(exchangeHandlerFactory, "Exchange handler factory"); this.h2Config = h2Config != null ? h2Config : H2Config.DEFAULT; this.charCodingConfig = charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT; this.streamListener = streamListener; this.frameFactory = frameFactory != null ? frameFactory : DefaultFrameFactory.INSTANCE; } public ServerH2StreamMultiplexerFactory( final HttpProcessor httpProcessor, final HandlerFactory exchangeHandlerFactory, final H2Config h2Config, final CharCodingConfig charCodingConfig, final H2StreamListener streamListener) { this(httpProcessor, exchangeHandlerFactory, h2Config, charCodingConfig, streamListener, null); } public ServerH2StreamMultiplexer create(final ProtocolIOSession ioSession) { return new ServerH2StreamMultiplexer( ioSession, frameFactory, httpProcessor, exchangeHandlerFactory, charCodingConfig, h2Config, streamListener); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2UpgradeHandler.java0100644 0000000 0000000 00000005773 00000250600 027776 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.impl.nio.HttpConnectionEventHandler; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.reactor.ProtocolUpgradeHandler; import org.apache.hc.core5.util.Args; /** * Protocol upgrade handler that upgrades the underlying {@link ProtocolIOSession} * to HTTP/2 in case of a successful protocol negotiation. * * @since 5.2 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) @Internal public class ServerH2UpgradeHandler implements ProtocolUpgradeHandler { private final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory; private final Callback exceptionCallback; public ServerH2UpgradeHandler(final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory, final Callback exceptionCallback) { this.http2StreamHandlerFactory = Args.notNull(http2StreamHandlerFactory, "HTTP/2 stream handler factory"); this.exceptionCallback = exceptionCallback; } @Override public void upgrade(final ProtocolIOSession ioSession, final FutureCallback callback) { final HttpConnectionEventHandler protocolNegotiator = new ServerH2PrefaceHandler( ioSession, http2StreamHandlerFactory, callback, exceptionCallback); ioSession.upgrade(protocolNegotiator); try { protocolNegotiator.connected(ioSession); } catch (final IOException ex) { protocolNegotiator.exception(ioSession, ex); } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttp1UpgradeHandler.java0100644 0000000 0000000 00000006311 00000250600 030512 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.nio.ServerHttp1IOEventHandler; import org.apache.hc.core5.http.impl.nio.ServerHttp1StreamDuplexerFactory; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.reactor.ProtocolUpgradeHandler; import org.apache.hc.core5.reactor.ssl.TlsDetails; import org.apache.hc.core5.util.Args; /** * Protocol upgrade handler that upgrades the underlying {@link ProtocolIOSession} * to HTTP/1.1 in case of a successful protocol negotiation or as a default fall-back. * * @since 5.2 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) @Internal public class ServerHttp1UpgradeHandler implements ProtocolUpgradeHandler { private final ServerHttp1StreamDuplexerFactory http1StreamHandlerFactory; public ServerHttp1UpgradeHandler(final ServerHttp1StreamDuplexerFactory http1StreamHandlerFactory) { this.http1StreamHandlerFactory = Args.notNull(http1StreamHandlerFactory, "HTTP/1.1 stream handler factory"); } @Override public void upgrade(final ProtocolIOSession ioSession, final FutureCallback callback) { final TlsDetails tlsDetails = ioSession.getTlsDetails(); final ServerHttp1IOEventHandler eventHandler = new ServerHttp1IOEventHandler(http1StreamHandlerFactory.create( tlsDetails != null ? URIScheme.HTTPS.id : URIScheme.HTTP.id, ioSession)); ioSession.upgrade(eventHandler); ioSession.upgrade(eventHandler); try { eventHandler.connected(ioSession); if (callback != null) { callback.completed(ioSession); } } catch (final IOException ex) { eventHandler.exception(ioSession, ex); } } } ././@LongLink0100644 0000000 0000000 00000000150 00000250600 011605 Lustar 0000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttpProtocolNegotiationStarter.javahttpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttpProtocolNegotiationStarter.j0100644 0000000 0000000 00000012205 00000250600 032422 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.nio.HttpConnectionEventHandler; import org.apache.hc.core5.http.impl.nio.ServerHttp1IOEventHandler; import org.apache.hc.core5.http.impl.nio.ServerHttp1StreamDuplexerFactory; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.ssl.ApplicationProtocol; import org.apache.hc.core5.reactor.EndpointParameters; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; /** * Server I/O event starter that prepares I/O sessions for an initial protocol handshake. * This class may return a different {@link org.apache.hc.core5.reactor.IOEventHandler} * implementation based on the current HTTP version policy. * * @since 5.1 */ @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) @Internal public class ServerHttpProtocolNegotiationStarter implements IOEventHandlerFactory { private final ServerHttp1StreamDuplexerFactory http1StreamHandlerFactory; private final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory; private final HttpVersionPolicy versionPolicy; private final TlsStrategy tlsStrategy; private final Timeout handshakeTimeout; private final Callback exceptionCallback; public ServerHttpProtocolNegotiationStarter( final ServerHttp1StreamDuplexerFactory http1StreamHandlerFactory, final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory, final HttpVersionPolicy versionPolicy, final TlsStrategy tlsStrategy, final Timeout handshakeTimeout, final Callback exceptionCallback) { this.http1StreamHandlerFactory = Args.notNull(http1StreamHandlerFactory, "HTTP/1.1 stream handler factory"); this.http2StreamHandlerFactory = Args.notNull(http2StreamHandlerFactory, "HTTP/2 stream handler factory"); this.versionPolicy = versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE; this.tlsStrategy = tlsStrategy; this.handshakeTimeout = handshakeTimeout; this.exceptionCallback = exceptionCallback; } @Override public HttpConnectionEventHandler createHandler(final ProtocolIOSession ioSession, final Object attachment) { HttpVersionPolicy endpointPolicy = versionPolicy; URIScheme uriScheme = URIScheme.HTTP; if (attachment instanceof EndpointParameters) { final EndpointParameters params = (EndpointParameters) attachment; if (params.getAttachment() instanceof HttpVersionPolicy) { endpointPolicy = (HttpVersionPolicy) params.getAttachment(); } if (tlsStrategy != null && URIScheme.HTTPS.same(params.getScheme())) { uriScheme = URIScheme.HTTPS; tlsStrategy.upgrade(ioSession, params, endpointPolicy, handshakeTimeout, null); } } ioSession.registerProtocol(ApplicationProtocol.HTTP_1_1.id, new ServerHttp1UpgradeHandler(http1StreamHandlerFactory)); ioSession.registerProtocol(ApplicationProtocol.HTTP_2.id, new ServerH2UpgradeHandler(http2StreamHandlerFactory, exceptionCallback)); switch (endpointPolicy) { case FORCE_HTTP_2: return new ServerH2PrefaceHandler(ioSession, http2StreamHandlerFactory, exceptionCallback); case FORCE_HTTP_1: return new ServerHttp1IOEventHandler(http1StreamHandlerFactory.create(uriScheme.id, ioSession)); default: return new HttpProtocolNegotiator(ioSession, null); } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerPushH2StreamHandler.java0100644 0000000 0000000 00000023562 00000250600 030476 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.impl.BasicHttpConnectionMetrics; import org.apache.hc.core5.http.impl.nio.MessageState; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.AsyncPushProducer; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http2.H2ConnectionException; import org.apache.hc.core5.http2.H2Error; import org.apache.hc.core5.http2.impl.DefaultH2RequestConverter; import org.apache.hc.core5.http2.impl.DefaultH2ResponseConverter; class ServerPushH2StreamHandler implements H2StreamHandler { private final H2StreamChannel outputChannel; private final DataStreamChannel dataChannel; private final HttpProcessor httpProcessor; private final BasicHttpConnectionMetrics connMetrics; private final AsyncPushProducer pushProducer; private final HttpCoreContext context; private final AtomicBoolean responseCommitted; private final AtomicBoolean failed; private final AtomicBoolean done; private volatile MessageState requestState; private volatile MessageState responseState; ServerPushH2StreamHandler( final H2StreamChannel outputChannel, final HttpProcessor httpProcessor, final BasicHttpConnectionMetrics connMetrics, final AsyncPushProducer pushProducer, final HttpCoreContext context) { this.outputChannel = outputChannel; this.dataChannel = new DataStreamChannel() { @Override public void requestOutput() { outputChannel.requestOutput(); } @Override public int write(final ByteBuffer src) throws IOException { return outputChannel.write(src); } @Override public void endStream(final List trailers) throws IOException { outputChannel.endStream(trailers); responseState = MessageState.COMPLETE; } @Override public void endStream() throws IOException { outputChannel.endStream(); responseState = MessageState.COMPLETE; } }; this.httpProcessor = httpProcessor; this.connMetrics = connMetrics; this.pushProducer = pushProducer; this.context = context; this.responseCommitted = new AtomicBoolean(); this.failed = new AtomicBoolean(); this.done = new AtomicBoolean(); this.requestState = MessageState.COMPLETE; this.responseState = MessageState.IDLE; } @Override public HandlerFactory getPushHandlerFactory() { return null; } @Override public void consumePromise(final List
headers) throws HttpException, IOException { throw new ProtocolException("Unexpected message promise"); } @Override public void consumeHeader(final List
requestHeaders, final boolean requestEndStream) throws HttpException, IOException { throw new ProtocolException("Unexpected message headers"); } @Override public void updateInputCapacity() throws IOException { } @Override public void consumeData(final ByteBuffer src, final boolean endStream) throws HttpException, IOException { throw new ProtocolException("Unexpected message data"); } @Override public boolean isOutputReady() { switch (responseState) { case IDLE: return true; case BODY: return pushProducer.available() > 0; default: return false; } } private void commitInformation(final HttpResponse response) throws IOException, HttpException { if (responseCommitted.get()) { throw new H2ConnectionException(H2Error.INTERNAL_ERROR, "Response already committed"); } final int status = response.getCode(); if (status < HttpStatus.SC_INFORMATIONAL || status >= HttpStatus.SC_SUCCESS) { throw new HttpException("Invalid intermediate response: " + status); } final List
responseHeaders = DefaultH2ResponseConverter.INSTANCE.convert(response); outputChannel.submit(responseHeaders, false); } private void commitResponse( final HttpResponse response, final EntityDetails responseEntityDetails) throws HttpException, IOException { if (responseCommitted.compareAndSet(false, true)) { context.setProtocolVersion(HttpVersion.HTTP_2); context.setResponse(response); httpProcessor.process(response, responseEntityDetails, context); final List
headers = DefaultH2ResponseConverter.INSTANCE.convert(response); outputChannel.submit(headers, responseEntityDetails == null); connMetrics.incrementResponseCount(); if (responseEntityDetails == null) { responseState = MessageState.COMPLETE; } else { responseState = MessageState.BODY; pushProducer.produce(outputChannel); } } } private void commitPromise( final HttpRequest promise, final AsyncPushProducer pushProducer) throws HttpException, IOException { context.setProtocolVersion(HttpVersion.HTTP_2); context.setRequest(promise); httpProcessor.process(promise, null, context); final List
headers = DefaultH2RequestConverter.INSTANCE.convert(promise); outputChannel.push(headers, pushProducer); connMetrics.incrementRequestCount(); } private void terminate() { outputChannel.terminate(); } @Override public void produceOutput() throws HttpException, IOException { switch (responseState) { case IDLE: responseState = MessageState.HEADERS; pushProducer.produceResponse(new ResponseChannel() { @Override public void sendInformation(final HttpResponse response, final HttpContext httpContext) throws HttpException, IOException { commitInformation(response); } @Override public void sendResponse( final HttpResponse response, final EntityDetails entityDetails, final HttpContext httpContext) throws HttpException, IOException { commitResponse(response, entityDetails); } @Override public void pushPromise( final HttpRequest promise, final AsyncPushProducer pushProducer, final HttpContext httpContext) throws HttpException, IOException { commitPromise(promise, pushProducer); } @Override public void terminateExchange() { terminate(); } }, context); break; case BODY: pushProducer.produce(dataChannel); break; } } @Override public void handle(final HttpException ex, final boolean endStream) throws HttpException, IOException { throw ex; } @Override public void failed(final Exception cause) { try { if (failed.compareAndSet(false, true)) { pushProducer.failed(cause); } } finally { releaseResources(); } } @Override public void releaseResources() { if (done.compareAndSet(false, true)) { requestState = MessageState.COMPLETE; responseState = MessageState.COMPLETE; pushProducer.releaseResources(); } } @Override public String toString() { return "[" + "requestState=" + requestState + ", responseState=" + responseState + ']'; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/package-info.java0100644 0000000 0000000 00000002472 00000250600 026045 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Default implementation of HTTP/2 transport based on * the asynchronous (non-blocking) I/O model. */ package org.apache.hc.core5.http2.impl.nio; httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/0040755 0000000 0000000 00000000000 00000250600 024671 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/CancellableExecution.java0100644 0000000 0000000 00000004713 00000250600 031607 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio.bootstrap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.concurrent.Cancellable; import org.apache.hc.core5.concurrent.CancellableDependency; final class CancellableExecution implements CancellableDependency { private final AtomicBoolean cancelled; private final AtomicReference dependencyRef; CancellableExecution() { this.cancelled = new AtomicBoolean(); this.dependencyRef = new AtomicReference<>(); } @Override public void setDependency(final Cancellable cancellable) { dependencyRef.set(cancellable); if (cancelled.get()) { final Cancellable dependency = dependencyRef.getAndSet(null); if (dependency != null) { dependency.cancel(); } } } @Override public boolean isCancelled() { return cancelled.get(); } @Override public boolean cancel() { if (cancelled.compareAndSet(false, true)) { final Cancellable dependency = dependencyRef.getAndSet(null); if (dependency != null) { dependency.cancel(); } return true; } return false; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/FilterEntry.java0100644 0000000 0000000 00000003306 00000250600 030002 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio.bootstrap; class FilterEntry { enum Position { BEFORE, AFTER, REPLACE, FIRST, LAST } final Position position; final String name; final T filterHandler; final String existing; FilterEntry( final Position position, final String name, final T filterHandler, final String existing) { this.position = position; this.name = name; this.filterHandler = filterHandler; this.existing = existing; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2AsyncRequester.java0100644 0000000 0000000 00000012570 00000250600 030705 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio.bootstrap; import java.util.concurrent.Future; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.concurrent.CallbackContribution; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.ssl.ApplicationProtocol; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.pool.ManagedConnPool; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.IOReactorMetricsListener; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.IOSessionListener; import org.apache.hc.core5.reactor.IOWorkerSelector; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.reactor.ssl.TlsDetails; import org.apache.hc.core5.util.Timeout; /** * Client side message exchange initiator capable of negotiating * HTTP/2 or HTTP/1.1 compatible connections. * * @since 5.0 */ public class H2AsyncRequester extends HttpAsyncRequester { private final HttpVersionPolicy versionPolicy; /** * Use {@link H2RequesterBootstrap} to create instances of this class. */ @Internal public H2AsyncRequester( final HttpVersionPolicy versionPolicy, final IOReactorConfig ioReactorConfig, final IOEventHandlerFactory eventHandlerFactory, final Decorator ioSessionDecorator, final Callback exceptionCallback, final IOSessionListener sessionListener, final ManagedConnPool connPool, final TlsStrategy tlsStrategy, final Timeout handshakeTimeout, final IOReactorMetricsListener threadPoolListener, final IOWorkerSelector workerSelector) { super(ioReactorConfig, eventHandlerFactory, ioSessionDecorator, exceptionCallback, sessionListener, connPool, tlsStrategy, handshakeTimeout, threadPoolListener, workerSelector); this.versionPolicy = versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE; } @Override protected Future doConnect( final HttpHost host, final Timeout timeout, final Object attachment, final FutureCallback callback) { return super.doConnect(host, timeout, attachment != null ? attachment : versionPolicy, callback); } @Override protected void doTlsUpgrade(final ProtocolIOSession ioSession, final NamedEndpoint endpoint, final FutureCallback callback) { super.doTlsUpgrade(ioSession, endpoint, new CallbackContribution(callback) { @Override public void completed(final ProtocolIOSession protocolSession) { final boolean switchProtocol; switch (versionPolicy) { case FORCE_HTTP_2: switchProtocol = true; break; case NEGOTIATE: final TlsDetails tlsDetails = protocolSession.getTlsDetails(); final String appProtocol = tlsDetails != null ? tlsDetails.getApplicationProtocol() : null; switchProtocol = ApplicationProtocol.HTTP_2.id.equals(appProtocol); break; default: switchProtocol = false; } if (switchProtocol) { protocolSession.switchProtocol(ApplicationProtocol.HTTP_2.id, callback); } else { if (callback != null) { callback.completed(protocolSession); } } } }); } } ././@LongLink0100644 0000000 0000000 00000000145 00000250600 011611 Lustar 0000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2MultiplexingRequester.javahttpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2MultiplexingRequester.java0100644 0000000 0000000 00000034232 00000250600 032310 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio.bootstrap; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.List; import java.util.Set; import java.util.concurrent.Future; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.concurrent.Cancellable; import org.apache.hc.core5.concurrent.CancellableDependency; import org.apache.hc.core5.concurrent.CompletingFutureContribution; import org.apache.hc.core5.concurrent.ComplexFuture; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.function.Resolver; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.impl.DefaultAddressResolver; import org.apache.hc.core5.http.impl.bootstrap.AsyncRequester; import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.AsyncRequestProducer; import org.apache.hc.core5.http.nio.AsyncResponseConsumer; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.nio.RequestChannel; import org.apache.hc.core5.http.nio.command.RequestExecutionCommand; import org.apache.hc.core5.http.nio.command.ShutdownCommand; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.http.nio.support.BasicClientExchangeHandler; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http2.nio.pool.H2ConnPool; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.reactor.Command; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.IOReactorMetricsListener; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.IOSessionListener; import org.apache.hc.core5.reactor.IOWorkerSelector; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; /** * HTTP/2 multiplexing client side message exchange initiator. * * @since 5.0 */ public class H2MultiplexingRequester extends AsyncRequester { private final H2ConnPool connPool; /** * Use {@link H2MultiplexingRequesterBootstrap} to create instances of this class. */ @Internal public H2MultiplexingRequester( final IOReactorConfig ioReactorConfig, final IOEventHandlerFactory eventHandlerFactory, final Decorator ioSessionDecorator, final Callback exceptionCallback, final IOSessionListener sessionListener, final Resolver addressResolver, final TlsStrategy tlsStrategy, final IOReactorMetricsListener threadPoolListener, final IOWorkerSelector workerSelector) { super(eventHandlerFactory, ioReactorConfig, ioSessionDecorator, exceptionCallback, sessionListener, ShutdownCommand.GRACEFUL_IMMEDIATE_CALLBACK, DefaultAddressResolver.INSTANCE, threadPoolListener, workerSelector); this.connPool = new H2ConnPool(this, addressResolver, tlsStrategy); } public void closeIdle(final TimeValue idleTime) { connPool.closeIdle(idleTime); } public Set getRoutes() { return connPool.getRoutes(); } public TimeValue getValidateAfterInactivity() { return connPool.getValidateAfterInactivity(); } public void setValidateAfterInactivity(final TimeValue timeValue) { connPool.setValidateAfterInactivity(timeValue); } /** * @since 5.3 */ public Cancellable execute( final HttpHost target, final AsyncClientExchangeHandler exchangeHandler, final HandlerFactory pushHandlerFactory, final Timeout timeout, final HttpContext context) { Args.notNull(exchangeHandler, "Exchange handler"); Args.notNull(timeout, "Timeout"); Args.notNull(context, "Context"); final CancellableExecution cancellableExecution = new CancellableExecution(); execute(target, exchangeHandler, pushHandlerFactory, cancellableExecution, timeout, context); return cancellableExecution; } public Cancellable execute( final AsyncClientExchangeHandler exchangeHandler, final HandlerFactory pushHandlerFactory, final Timeout timeout, final HttpContext context) { return execute(null, exchangeHandler, pushHandlerFactory, timeout, context); } /** * @since 5.3 */ public Cancellable execute( final HttpHost target, final AsyncClientExchangeHandler exchangeHandler, final Timeout timeout, final HttpContext context) { return execute(target, exchangeHandler, null, timeout, context); } public Cancellable execute( final AsyncClientExchangeHandler exchangeHandler, final Timeout timeout, final HttpContext context) { return execute(null, exchangeHandler, null, timeout, context); } private void execute( final HttpHost target, final AsyncClientExchangeHandler exchangeHandler, final HandlerFactory pushHandlerFactory, final CancellableDependency cancellableDependency, final Timeout timeout, final HttpContext context) { Args.notNull(exchangeHandler, "Exchange handler"); Args.notNull(timeout, "Timeout"); Args.notNull(context, "Context"); try { exchangeHandler.produceRequest((request, entityDetails, httpContext) -> { final HttpHost host = target != null ? target : defaultTarget(request); if (request.getAuthority() == null) { request.setAuthority(new URIAuthority(host)); } connPool.getSession(host, timeout, new FutureCallback() { @Override public void completed(final IOSession ioSession) { ioSession.enqueue(new RequestExecutionCommand(new AsyncClientExchangeHandler() { @Override public void releaseResources() { exchangeHandler.releaseResources(); } @Override public void produceRequest(final RequestChannel channel, final HttpContext httpContext) throws HttpException, IOException { channel.sendRequest(request, entityDetails, httpContext); } @Override public int available() { return exchangeHandler.available(); } @Override public void produce(final DataStreamChannel channel) throws IOException { exchangeHandler.produce(channel); } @Override public void consumeInformation(final HttpResponse response, final HttpContext httpContext) throws HttpException, IOException { exchangeHandler.consumeInformation(response, httpContext); } @Override public void consumeResponse( final HttpResponse response, final EntityDetails entityDetails, final HttpContext httpContext) throws HttpException, IOException { exchangeHandler.consumeResponse(response, entityDetails, httpContext); } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { exchangeHandler.updateCapacity(capacityChannel); } @Override public void consume(final ByteBuffer src) throws IOException { exchangeHandler.consume(src); } @Override public void streamEnd(final List trailers) throws HttpException, IOException { exchangeHandler.streamEnd(trailers); } @Override public void cancel() { exchangeHandler.cancel(); } @Override public void failed(final Exception cause) { exchangeHandler.failed(cause); } }, pushHandlerFactory, cancellableDependency, context), Command.Priority.NORMAL); if (!ioSession.isOpen()) { exchangeHandler.failed(new ConnectionClosedException()); } } @Override public void failed(final Exception ex) { exchangeHandler.failed(ex); } @Override public void cancelled() { exchangeHandler.cancel(); } }); }, context); } catch (final IOException | HttpException ex) { exchangeHandler.failed(ex); } } /** * @param The result type returned by the Future's {@code get} method. * @since 5.3 */ public final Future execute( final HttpHost target, final AsyncRequestProducer requestProducer, final AsyncResponseConsumer responseConsumer, final HandlerFactory pushHandlerFactory, final Timeout timeout, final HttpContext context, final FutureCallback callback) { Args.notNull(requestProducer, "Request producer"); Args.notNull(responseConsumer, "Response consumer"); Args.notNull(timeout, "Timeout"); final ComplexFuture future = new ComplexFuture<>(callback); final AsyncClientExchangeHandler exchangeHandler = new BasicClientExchangeHandler<>( requestProducer, responseConsumer, new CompletingFutureContribution(future)); execute(target, exchangeHandler, pushHandlerFactory, future, timeout, context != null ? context : HttpCoreContext.create()); return future; } public final Future execute( final AsyncRequestProducer requestProducer, final AsyncResponseConsumer responseConsumer, final HandlerFactory pushHandlerFactory, final Timeout timeout, final HttpContext context, final FutureCallback callback) { return execute(null, requestProducer, responseConsumer, pushHandlerFactory, timeout, context, callback); } /** * @param The result type returned by the Future's {@code get} method. * @since 5.3 */ public final Future execute( final HttpHost target, final AsyncRequestProducer requestProducer, final AsyncResponseConsumer responseConsumer, final Timeout timeout, final HttpContext context, final FutureCallback callback) { return execute(target, requestProducer, responseConsumer, null, timeout, context, callback); } public final Future execute( final AsyncRequestProducer requestProducer, final AsyncResponseConsumer responseConsumer, final Timeout timeout, final HttpContext context, final FutureCallback callback) { return execute(null, requestProducer, responseConsumer, null, timeout, context, callback); } public final Future execute( final AsyncRequestProducer requestProducer, final AsyncResponseConsumer responseConsumer, final Timeout timeout, final FutureCallback callback) { return execute(null, requestProducer, responseConsumer, null, timeout, null, callback); } @Internal public H2ConnPool getConnPool() { return connPool; } } ././@LongLink0100644 0000000 0000000 00000000156 00000250600 011613 Lustar 0000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2MultiplexingRequesterBootstrap.javahttpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2MultiplexingRequesterBoots0100644 0000000 0000000 00000024045 00000250600 032400 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio.bootstrap; import java.util.ArrayList; import java.util.List; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.impl.DefaultAddressResolver; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http.protocol.UriPatternType; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.frame.FrameFactory; import org.apache.hc.core5.http2.impl.H2Processors; import org.apache.hc.core5.http2.impl.nio.ClientH2PrefaceHandler; import org.apache.hc.core5.http2.impl.nio.ClientH2StreamMultiplexerFactory; import org.apache.hc.core5.http2.impl.nio.H2StreamListener; import org.apache.hc.core5.http2.nio.support.DefaultAsyncPushConsumerFactory; import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.IOReactorMetricsListener; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.IOSessionListener; import org.apache.hc.core5.util.Args; /** * {@link H2MultiplexingRequester} bootstrap. * * @since 5.0 */ public class H2MultiplexingRequesterBootstrap { private final List>> routeEntries; private UriPatternType uriPatternType; private IOReactorConfig ioReactorConfig; private HttpProcessor httpProcessor; private CharCodingConfig charCodingConfig; private H2Config h2Config; private TlsStrategy tlsStrategy; private boolean strictALPNHandshake; private Decorator ioSessionDecorator; private Callback exceptionCallback; private IOSessionListener sessionListener; private H2StreamListener streamListener; private FrameFactory frameFactory; private IOReactorMetricsListener threadPoolListener; private H2MultiplexingRequesterBootstrap() { this.routeEntries = new ArrayList<>(); } public static H2MultiplexingRequesterBootstrap bootstrap() { return new H2MultiplexingRequesterBootstrap(); } /** * Sets I/O reactor configuration. * * @return this instance. */ public final H2MultiplexingRequesterBootstrap setIOReactorConfig(final IOReactorConfig ioReactorConfig) { this.ioReactorConfig = ioReactorConfig; return this; } /** * Sets {@link HttpProcessor} instance. * * @return this instance. */ public final H2MultiplexingRequesterBootstrap setHttpProcessor(final HttpProcessor httpProcessor) { this.httpProcessor = httpProcessor; return this; } /** * Sets HTTP/2 protocol parameters * * @return this instance. */ public final H2MultiplexingRequesterBootstrap setH2Config(final H2Config h2Config) { this.h2Config = h2Config; return this; } /** * Sets message char coding. * * @return this instance. */ public final H2MultiplexingRequesterBootstrap setCharCodingConfig(final CharCodingConfig charCodingConfig) { this.charCodingConfig = charCodingConfig; return this; } /** * Sets {@link TlsStrategy} instance. * * @return this instance. */ public final H2MultiplexingRequesterBootstrap setTlsStrategy(final TlsStrategy tlsStrategy) { this.tlsStrategy = tlsStrategy; return this; } public final H2MultiplexingRequesterBootstrap setStrictALPNHandshake(final boolean strictALPNHandshake) { this.strictALPNHandshake = strictALPNHandshake; return this; } /** * Sets {@link IOSession} {@link Decorator} instance. * * @return this instance. */ public final H2MultiplexingRequesterBootstrap setIOSessionDecorator(final Decorator ioSessionDecorator) { this.ioSessionDecorator = ioSessionDecorator; return this; } /** * Sets {@link Exception} {@link Callback} instance. * * @return this instance. */ public final H2MultiplexingRequesterBootstrap setExceptionCallback(final Callback exceptionCallback) { this.exceptionCallback = exceptionCallback; return this; } /** * Sets {@link IOSessionListener} instance. * * @return this instance. */ public final H2MultiplexingRequesterBootstrap setIOSessionListener(final IOSessionListener sessionListener) { this.sessionListener = sessionListener; return this; } /** * Sets {@link IOReactorMetricsListener} instance. * * @return this instance. * @since 5.4 */ public final H2MultiplexingRequesterBootstrap setIOReactorMetricsListener(final IOReactorMetricsListener threadPoolListener) { this.threadPoolListener = threadPoolListener; return this; } /** * Sets {@link H2StreamListener} instance. * * @return this instance. */ public final H2MultiplexingRequesterBootstrap setStreamListener(final H2StreamListener streamListener) { this.streamListener = streamListener; return this; } /** * Sets {@link FrameFactory} instance. * * @since 5.4 * @return this instance. */ public final H2MultiplexingRequesterBootstrap setStreamListener(final FrameFactory frameFactory) { this.frameFactory = frameFactory; return this; } /** * Sets {@link UriPatternType} for handler registration. * * @return this instance. */ public final H2MultiplexingRequesterBootstrap setUriPatternType(final UriPatternType uriPatternType) { this.uriPatternType = uriPatternType; return this; } /** * Registers the given {@link AsyncPushConsumer} {@link Supplier} as a default handler for URIs * matching the given pattern. * * @param uriPattern the pattern to register the handler for. * @param supplier the handler supplier. * @return this instance. */ public final H2MultiplexingRequesterBootstrap register(final String uriPattern, final Supplier supplier) { Args.notBlank(uriPattern, "URI pattern"); Args.notNull(supplier, "Push consumer supplier"); routeEntries.add(new RequestRouter.Entry<>(uriPattern, supplier)); return this; } /** * Registers the given {@link AsyncPushConsumer} {@link Supplier} as a handler for URIs * matching the given host and the pattern. * * @param hostname the host name * @param uriPattern the pattern to register the handler for. * @param supplier the handler supplier. * @return this instance. * * @since 5.3 */ public final H2MultiplexingRequesterBootstrap register(final String hostname, final String uriPattern, final Supplier supplier) { Args.notBlank(hostname, "Hostname"); Args.notBlank(uriPattern, "URI pattern"); Args.notNull(supplier, "Push consumer supplier"); routeEntries.add(new RequestRouter.Entry<>(hostname, uriPattern, supplier)); return this; } /** * @return this instance. * @deprecated Use {@link #register(String, String, Supplier)}. */ @Deprecated public final H2MultiplexingRequesterBootstrap registerVirtual(final String hostname, final String uriPattern, final Supplier supplier) { return register(hostname, uriPattern, supplier); } public H2MultiplexingRequester create() { final RequestRouter> requestRouter = RequestRouter.create( null, uriPatternType, routeEntries, RequestRouter.LOCAL_AUTHORITY_RESOLVER, null); final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory = new ClientH2StreamMultiplexerFactory( httpProcessor != null ? httpProcessor : H2Processors.client(), new DefaultAsyncPushConsumerFactory(requestRouter), h2Config != null ? h2Config : H2Config.DEFAULT, charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT, streamListener, frameFactory); return new H2MultiplexingRequester( ioReactorConfig, (ioSession, attachment) -> new ClientH2PrefaceHandler(ioSession, http2StreamHandlerFactory, strictALPNHandshake, exceptionCallback), ioSessionDecorator, exceptionCallback, sessionListener, DefaultAddressResolver.INSTANCE, tlsStrategy != null ? tlsStrategy : new H2ClientTlsStrategy(), threadPoolListener, null); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2RequesterBootstrap.java0100644 0000000 0000000 00000037361 00000250600 031612 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio.bootstrap; import java.util.ArrayList; import java.util.List; import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.HttpProcessors; import org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexerFactory; import org.apache.hc.core5.http.impl.nio.DefaultHttpRequestWriterFactory; import org.apache.hc.core5.http.impl.nio.DefaultHttpResponseParserFactory; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http.protocol.UriPatternType; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.frame.FrameFactory; import org.apache.hc.core5.http2.impl.H2Processors; import org.apache.hc.core5.http2.impl.nio.ClientH2StreamMultiplexerFactory; import org.apache.hc.core5.http2.impl.nio.ClientHttpProtocolNegotiationStarter; import org.apache.hc.core5.http2.impl.nio.H2StreamListener; import org.apache.hc.core5.http2.nio.support.DefaultAsyncPushConsumerFactory; import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy; import org.apache.hc.core5.pool.ConnPoolListener; import org.apache.hc.core5.pool.DefaultDisposalCallback; import org.apache.hc.core5.pool.LaxConnPool; import org.apache.hc.core5.pool.ManagedConnPool; import org.apache.hc.core5.pool.PoolConcurrencyPolicy; import org.apache.hc.core5.pool.PoolReusePolicy; import org.apache.hc.core5.pool.RouteSegmentedConnPool; import org.apache.hc.core5.pool.StrictConnPool; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.IOReactorMetricsListener; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.IOSessionListener; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; /** * {@link H2AsyncRequester} bootstrap. * * @since 5.0 */ public class H2RequesterBootstrap { private final List>> routeEntries; private UriPatternType uriPatternType; private IOReactorConfig ioReactorConfig; private HttpProcessor httpProcessor; private CharCodingConfig charCodingConfig; private HttpVersionPolicy versionPolicy; private H2Config h2Config; private Http1Config http1Config; private int defaultMaxPerRoute; private int maxTotal; private TimeValue timeToLive; private PoolReusePolicy poolReusePolicy; private PoolConcurrencyPolicy poolConcurrencyPolicy; private TlsStrategy tlsStrategy; private Timeout handshakeTimeout; private Decorator ioSessionDecorator; private Callback exceptionCallback; private IOSessionListener sessionListener; private H2StreamListener streamListener; private Http1StreamListener http1StreamListener; private ConnPoolListener connPoolListener; private IOReactorMetricsListener threadPoolListener; private FrameFactory frameFactory; private H2RequesterBootstrap() { this.routeEntries = new ArrayList<>(); } public static H2RequesterBootstrap bootstrap() { return new H2RequesterBootstrap(); } /** * Sets I/O reactor configuration. * * @return this instance. */ public final H2RequesterBootstrap setIOReactorConfig(final IOReactorConfig ioReactorConfig) { this.ioReactorConfig = ioReactorConfig; return this; } /** * Sets {@link HttpProcessor} instance. * * @return this instance. */ public final H2RequesterBootstrap setHttpProcessor(final HttpProcessor httpProcessor) { this.httpProcessor = httpProcessor; return this; } /** * Sets HTTP protocol version policy * * @return this instance. */ public final H2RequesterBootstrap setVersionPolicy(final HttpVersionPolicy versionPolicy) { this.versionPolicy = versionPolicy; return this; } /** * Sets HTTP/2 protocol parameters * * @return this instance. */ public final H2RequesterBootstrap setH2Config(final H2Config h2Config) { this.h2Config = h2Config; return this; } /** * Sets HTTP/1.1 protocol parameters * * @return this instance. */ public final H2RequesterBootstrap setHttp1Config(final Http1Config http1Config) { this.http1Config = http1Config; return this; } /** * Sets message char coding. * * @return this instance. */ public final H2RequesterBootstrap setCharCodingConfig(final CharCodingConfig charCodingConfig) { this.charCodingConfig = charCodingConfig; return this; } public final H2RequesterBootstrap setDefaultMaxPerRoute(final int defaultMaxPerRoute) { this.defaultMaxPerRoute = defaultMaxPerRoute; return this; } public final H2RequesterBootstrap setMaxTotal(final int maxTotal) { this.maxTotal = maxTotal; return this; } public final H2RequesterBootstrap setTimeToLive(final TimeValue timeToLive) { this.timeToLive = timeToLive; return this; } /** * Sets {@link PoolReusePolicy} instance. * * @return this instance. */ public final H2RequesterBootstrap setPoolReusePolicy(final PoolReusePolicy poolReusePolicy) { this.poolReusePolicy = poolReusePolicy; return this; } /** * Sets {@link PoolConcurrencyPolicy} instance. * * @return this instance. */ @Experimental public final H2RequesterBootstrap setPoolConcurrencyPolicy(final PoolConcurrencyPolicy poolConcurrencyPolicy) { this.poolConcurrencyPolicy = poolConcurrencyPolicy; return this; } /** * Sets {@link TlsStrategy} instance. * * @return this instance. */ public final H2RequesterBootstrap setTlsStrategy(final TlsStrategy tlsStrategy) { this.tlsStrategy = tlsStrategy; return this; } public final H2RequesterBootstrap setHandshakeTimeout(final Timeout handshakeTimeout) { this.handshakeTimeout = handshakeTimeout; return this; } /** * Sets {@link IOSession} {@link Decorator} instance. * * @return this instance. */ public final H2RequesterBootstrap setIOSessionDecorator(final Decorator ioSessionDecorator) { this.ioSessionDecorator = ioSessionDecorator; return this; } /** * Sets {@link Exception} {@link Callback} instance. * * @return this instance. */ public final H2RequesterBootstrap setExceptionCallback(final Callback exceptionCallback) { this.exceptionCallback = exceptionCallback; return this; } /** * Sets {@link IOSessionListener} instance. * * @return this instance. */ public final H2RequesterBootstrap setIOSessionListener(final IOSessionListener sessionListener) { this.sessionListener = sessionListener; return this; } /** * Sets {@link IOReactorMetricsListener} instance. * * @return this instance. * @since 5.4 */ public final H2RequesterBootstrap setIOReactorMetricsListener(final IOReactorMetricsListener threadPoolListener) { this.threadPoolListener = threadPoolListener; return this; } /** * Sets {@link H2StreamListener} instance. * * @return this instance. */ public final H2RequesterBootstrap setStreamListener(final H2StreamListener streamListener) { this.streamListener = streamListener; return this; } /** * Sets {@link Http1StreamListener} instance. * * @return this instance. */ public final H2RequesterBootstrap setStreamListener(final Http1StreamListener http1StreamListener) { this.http1StreamListener = http1StreamListener; return this; } /** * Sets {@link ConnPoolListener} instance. * * @return this instance. */ public final H2RequesterBootstrap setConnPoolListener(final ConnPoolListener connPoolListener) { this.connPoolListener = connPoolListener; return this; } /** * Sets {@link UriPatternType} for handler registration. * * @return this instance. */ public final H2RequesterBootstrap setUriPatternType(final UriPatternType uriPatternType) { this.uriPatternType = uriPatternType; return this; } /** * Sets {@link FrameFactory} instance. * * @since 5.4 * @return this instance. */ public final H2RequesterBootstrap setFrameFactory(final FrameFactory frameFactory) { this.frameFactory = frameFactory; return this; } /** * Registers the given {@link AsyncPushConsumer} {@link Supplier} as a default handler for URIs * matching the given pattern. * * @param uriPattern the pattern to register the handler for. * @param supplier the handler supplier. * @return this instance. */ public final H2RequesterBootstrap register(final String uriPattern, final Supplier supplier) { Args.notBlank(uriPattern, "URI pattern"); Args.notNull(supplier, "Push consumer supplier"); routeEntries.add(new RequestRouter.Entry<>(uriPattern, supplier)); return this; } /** * Registers the given {@link AsyncPushConsumer} {@link Supplier} as a handler for URIs * matching the given host and the pattern. * * @param hostname the host name * @param uriPattern the pattern to register the handler for. * @param supplier the handler supplier. * @return this instance. * * @since 5.3 */ public final H2RequesterBootstrap register(final String hostname, final String uriPattern, final Supplier supplier) { Args.notBlank(hostname, "Hostname"); Args.notBlank(uriPattern, "URI pattern"); Args.notNull(supplier, "Push consumer supplier"); routeEntries.add(new RequestRouter.Entry<>(hostname, uriPattern, supplier)); return this; } /** * @return this instance. * @deprecated Use {@link #register(String, String, Supplier)}. */ @Deprecated public final H2RequesterBootstrap registerVirtual(final String hostname, final String uriPattern, final Supplier supplier) { return register(hostname, uriPattern, supplier); } public H2AsyncRequester create() { final ManagedConnPool connPool; switch (poolConcurrencyPolicy != null ? poolConcurrencyPolicy : PoolConcurrencyPolicy.STRICT) { case LAX: connPool = new LaxConnPool<>( defaultMaxPerRoute > 0 ? defaultMaxPerRoute : 20, timeToLive, poolReusePolicy, new DefaultDisposalCallback<>(), connPoolListener); break; case OFFLOCK: connPool = new RouteSegmentedConnPool<>( defaultMaxPerRoute > 0 ? defaultMaxPerRoute : 20, maxTotal > 0 ? maxTotal : 50, timeToLive, poolReusePolicy, new DefaultDisposalCallback<>()); break; case STRICT: default: connPool = new StrictConnPool<>( defaultMaxPerRoute > 0 ? defaultMaxPerRoute : 20, maxTotal > 0 ? maxTotal : 50, timeToLive, poolReusePolicy, new DefaultDisposalCallback<>(), connPoolListener); break; } final RequestRouter> requestRouter = RequestRouter.create( null, uriPatternType, routeEntries, RequestRouter.LOCAL_AUTHORITY_RESOLVER, null); final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory = new ClientH2StreamMultiplexerFactory( httpProcessor != null ? httpProcessor : H2Processors.client(), new DefaultAsyncPushConsumerFactory(requestRouter), h2Config != null ? h2Config : H2Config.DEFAULT, charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT, streamListener, frameFactory); final TlsStrategy actualTlsStrategy = tlsStrategy != null ? tlsStrategy : new H2ClientTlsStrategy(); final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory = new ClientHttp1StreamDuplexerFactory( httpProcessor != null ? httpProcessor : HttpProcessors.client(), http1Config != null ? http1Config : Http1Config.DEFAULT, charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT, DefaultConnectionReuseStrategy.INSTANCE, new DefaultHttpResponseParserFactory(http1Config), DefaultHttpRequestWriterFactory.INSTANCE, DefaultContentLengthStrategy.INSTANCE, DefaultContentLengthStrategy.INSTANCE, http1StreamListener); final IOEventHandlerFactory ioEventHandlerFactory = new ClientHttpProtocolNegotiationStarter( http1StreamHandlerFactory, http2StreamHandlerFactory, versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE, actualTlsStrategy, handshakeTimeout, exceptionCallback); return new H2AsyncRequester( versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE, ioReactorConfig, ioEventHandlerFactory, ioSessionDecorator, exceptionCallback, sessionListener, connPool, actualTlsStrategy, handshakeTimeout, threadPoolListener, null); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2ServerBootstrap.java0100644 0000000 0000000 00000053620 00000250600 031075 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio.bootstrap; import java.util.ArrayList; import java.util.List; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.HttpRequestMapper; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.config.NamedElementChain; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.HttpProcessors; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.impl.bootstrap.StandardFilter; import org.apache.hc.core5.http.impl.nio.DefaultHttpRequestParserFactory; import org.apache.hc.core5.http.impl.nio.DefaultHttpResponseWriterFactory; import org.apache.hc.core5.http.impl.nio.ServerHttp1StreamDuplexerFactory; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncFilterHandler; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.AsyncServerRequestHandler; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.http.nio.support.AsyncServerExpectationFilter; import org.apache.hc.core5.http.nio.support.AsyncServerFilterChainElement; import org.apache.hc.core5.http.nio.support.AsyncServerFilterChainExchangeHandlerFactory; import org.apache.hc.core5.http.nio.support.BasicAsyncServerExpectationDecorator; import org.apache.hc.core5.http.nio.support.BasicServerExchangeHandler; import org.apache.hc.core5.http.nio.support.DefaultAsyncResponseExchangeHandlerFactory; import org.apache.hc.core5.http.nio.support.TerminalAsyncServerFilter; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http.protocol.UriPatternType; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.frame.FrameFactory; import org.apache.hc.core5.http2.impl.H2Processors; import org.apache.hc.core5.http2.impl.nio.H2StreamListener; import org.apache.hc.core5.http2.impl.nio.ServerH2StreamMultiplexerFactory; import org.apache.hc.core5.http2.impl.nio.ServerHttpProtocolNegotiationStarter; import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy; import org.apache.hc.core5.net.InetAddressUtils; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.IOReactorMetricsListener; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.IOSessionListener; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; /** * HTTP/2 capable {@link HttpAsyncServer} bootstrap. * * @since 5.0 */ @SuppressWarnings("deprecation") public class H2ServerBootstrap { private final List>> routeEntries; private final List> filters; private String canonicalHostName; private org.apache.hc.core5.http.protocol.LookupRegistry> lookupRegistry; private HttpRequestMapper> requestRouter; private IOReactorConfig ioReactorConfig; private HttpProcessor httpProcessor; private CharCodingConfig charCodingConfig; private HttpVersionPolicy versionPolicy; private H2Config h2Config; private Http1Config http1Config; private TlsStrategy tlsStrategy; private Timeout handshakeTimeout; private Decorator ioSessionDecorator; private Callback exceptionCallback; private IOSessionListener sessionListener; private H2StreamListener h2StreamListener; private Http1StreamListener http1StreamListener; private IOReactorMetricsListener threadPoolListener; private FrameFactory frameFactory; private H2ServerBootstrap() { this.routeEntries = new ArrayList<>(); this.filters = new ArrayList<>(); } public static H2ServerBootstrap bootstrap() { return new H2ServerBootstrap(); } /** * Sets canonical name (fully qualified domain name) of the server. * * @return this instance. * @since 5.0 */ public final H2ServerBootstrap setCanonicalHostName(final String canonicalHostName) { this.canonicalHostName = canonicalHostName; return this; } /** * Sets I/O reactor configuration. * * @return this instance. */ public final H2ServerBootstrap setIOReactorConfig(final IOReactorConfig ioReactorConfig) { this.ioReactorConfig = ioReactorConfig; return this; } /** * Sets {@link HttpProcessor} instance. * * @return this instance. */ public final H2ServerBootstrap setHttpProcessor(final HttpProcessor httpProcessor) { this.httpProcessor = httpProcessor; return this; } /** * Sets HTTP protocol version policy * * @return this instance. */ public final H2ServerBootstrap setVersionPolicy(final HttpVersionPolicy versionPolicy) { this.versionPolicy = versionPolicy; return this; } /** * Sets HTTP/2 protocol parameters * * @return this instance. */ public final H2ServerBootstrap setH2Config(final H2Config h2Config) { this.h2Config = h2Config; return this; } /** * Sets HTTP/1.1 protocol parameters * * @return this instance. */ public final H2ServerBootstrap setHttp1Config(final Http1Config http1Config) { this.http1Config = http1Config; return this; } /** * Sets message char coding. * * @return this instance. */ public final H2ServerBootstrap setCharset(final CharCodingConfig charCodingConfig) { this.charCodingConfig = charCodingConfig; return this; } /** * Sets {@link TlsStrategy} instance. * * @return this instance. */ public final H2ServerBootstrap setTlsStrategy(final TlsStrategy tlsStrategy) { this.tlsStrategy = tlsStrategy; return this; } public final H2ServerBootstrap setHandshakeTimeout(final Timeout handshakeTimeout) { this.handshakeTimeout = handshakeTimeout; return this; } /** * Sets {@link IOSession} {@link Decorator} instance. * * @return this instance. */ public final H2ServerBootstrap setIOSessionDecorator(final Decorator ioSessionDecorator) { this.ioSessionDecorator = ioSessionDecorator; return this; } /** * Sets {@link IOReactorMetricsListener} instance. * * @return this instance. * @since 5.4 */ public final H2ServerBootstrap setIOReactorMetricsListener(final IOReactorMetricsListener threadPoolListener) { this.threadPoolListener = threadPoolListener; return this; } /** * Sets {@link Exception} {@link Callback} instance. * * @return this instance. */ public final H2ServerBootstrap setExceptionCallback(final Callback exceptionCallback) { this.exceptionCallback = exceptionCallback; return this; } /** * Sets {@link IOSessionListener} instance. * * @return this instance. */ public final H2ServerBootstrap setIOSessionListener(final IOSessionListener sessionListener) { this.sessionListener = sessionListener; return this; } /** * Sets {@link H2StreamListener} instance. * * @return this instance. */ public final H2ServerBootstrap setStreamListener(final H2StreamListener h2StreamListener) { this.h2StreamListener = h2StreamListener; return this; } /** * Sets {@link FrameFactory} instance. * * @since 5.4 * @return this instance. */ public final H2ServerBootstrap setFrameFactory(final FrameFactory frameFactory) { this.frameFactory = frameFactory; return this; } /** * Sets {@link Http1StreamListener} instance. * * @return this instance. */ public final H2ServerBootstrap setStreamListener(final Http1StreamListener http1StreamListener) { this.http1StreamListener = http1StreamListener; return this; } /** * @return this instance. * @deprecated Use {@link RequestRouter}. */ @Deprecated public final H2ServerBootstrap setLookupRegistry(final org.apache.hc.core5.http.protocol.LookupRegistry> lookupRegistry) { this.lookupRegistry = lookupRegistry; return this; } /** * Sets {@link HttpRequestMapper} instance. * * @return this instance. * @see org.apache.hc.core5.http.impl.routing.RequestRouter * @since 5.3 */ public final H2ServerBootstrap setRequestRouter(final HttpRequestMapper> requestRouter) { this.requestRouter = requestRouter; return this; } /** * Registers the given {@link AsyncServerExchangeHandler} {@link Supplier} as a default handler for URIs * matching the given pattern. * * @param uriPattern the pattern to register the handler for. * @param supplier the handler supplier. * @return this instance. */ public final H2ServerBootstrap register(final String uriPattern, final Supplier supplier) { Args.notBlank(uriPattern, "URI pattern"); Args.notNull(supplier, "Exchange handler supplier"); routeEntries.add(new RequestRouter.Entry<>(uriPattern, supplier)); return this; } /** * Registers the given {@link AsyncServerExchangeHandler} {@link Supplier} as a handler for URIs * matching the given host and the pattern. * * @param hostname the host name * @param uriPattern the pattern to register the handler for. * @param supplier the handler supplier. * @return this instance. * * @since 5.3 */ public final H2ServerBootstrap register(final String hostname, final String uriPattern, final Supplier supplier) { Args.notBlank(hostname, "Hostname"); Args.notBlank(uriPattern, "URI pattern"); Args.notNull(supplier, "Exchange handler supplier"); routeEntries.add(new RequestRouter.Entry<>(hostname, uriPattern, supplier)); return this; } /** * @return this instance. * @deprecated Use {@link #register(String, String, Supplier)}. */ @Deprecated public final H2ServerBootstrap registerVirtual(final String hostname, final String uriPattern, final Supplier supplier) { return register(hostname, uriPattern, supplier); } /** * Registers the given {@link AsyncServerRequestHandler} as a default handler for URIs * matching the given pattern. * * @param request handler representation. * @param uriPattern the pattern to register the handler for. * @param requestHandler the handler. * @return this instance. */ public final H2ServerBootstrap register( final String uriPattern, final AsyncServerRequestHandler requestHandler) { register(uriPattern, () -> new BasicServerExchangeHandler<>(requestHandler)); return this; } /** * Registers the given {@link AsyncServerRequestHandler} as a handler for URIs * matching the given host and the pattern. * * @param request handler representation. * @param hostname the host name * @param uriPattern the pattern to register the handler for. * @param requestHandler the handler. * @return this instance. * * @since 5.3 */ public final H2ServerBootstrap register( final String hostname, final String uriPattern, final AsyncServerRequestHandler requestHandler) { registerVirtual(hostname, uriPattern, () -> new BasicServerExchangeHandler<>(requestHandler)); return this; } /** * @param request handler representation. * @return this instance. * @deprecated Use {@link #register(String, String, Supplier)}. */ @Deprecated public final H2ServerBootstrap registerVirtual( final String hostname, final String uriPattern, final AsyncServerRequestHandler requestHandler) { return register(hostname, uriPattern, requestHandler); } /** * Adds the filter before the filter with the given name. * * @return this instance. */ public final H2ServerBootstrap addFilterBefore(final String existing, final String name, final AsyncFilterHandler filterHandler) { Args.notBlank(existing, "Existing"); Args.notBlank(name, "Name"); Args.notNull(filterHandler, "Filter handler"); filters.add(new FilterEntry<>(FilterEntry.Position.BEFORE, name, filterHandler, existing)); return this; } /** * Adds the filter after the filter with the given name. * * @return this instance. */ public final H2ServerBootstrap addFilterAfter(final String existing, final String name, final AsyncFilterHandler filterHandler) { Args.notBlank(existing, "Existing"); Args.notBlank(name, "Name"); Args.notNull(filterHandler, "Filter handler"); filters.add(new FilterEntry<>(FilterEntry.Position.AFTER, name, filterHandler, existing)); return this; } /** * Replace an existing filter with the given name with new filter. * * @return this instance. */ public final H2ServerBootstrap replaceFilter(final String existing, final AsyncFilterHandler filterHandler) { Args.notBlank(existing, "Existing"); Args.notNull(filterHandler, "Filter handler"); filters.add(new FilterEntry<>(FilterEntry.Position.REPLACE, existing, filterHandler, existing)); return this; } /** * Add an filter to the head of the processing list. * * @return this instance. */ public final H2ServerBootstrap addFilterFirst(final String name, final AsyncFilterHandler filterHandler) { Args.notNull(name, "Name"); Args.notNull(filterHandler, "Filter handler"); filters.add(new FilterEntry<>(FilterEntry.Position.FIRST, name, filterHandler, null)); return this; } /** * Add an filter to the tail of the processing list. * * @return this instance. */ public final H2ServerBootstrap addFilterLast(final String name, final AsyncFilterHandler filterHandler) { Args.notNull(name, "Name"); Args.notNull(filterHandler, "Filter handler"); filters.add(new FilterEntry<>(FilterEntry.Position.LAST, name, filterHandler, null)); return this; } public HttpAsyncServer create() { final String actualCanonicalHostName = canonicalHostName != null ? canonicalHostName : InetAddressUtils.getCanonicalLocalHostName(); final HttpRequestMapper> requestRouterCopy; if (lookupRegistry != null && requestRouter == null) { final org.apache.hc.core5.http.protocol.RequestHandlerRegistry> handlerRegistry = new org.apache.hc.core5.http.protocol.RequestHandlerRegistry<>( actualCanonicalHostName, () -> lookupRegistry != null ? lookupRegistry : new org.apache.hc.core5.http.protocol.UriPatternMatcher<>()); for (final RequestRouter.Entry> entry: routeEntries) { handlerRegistry.register(entry.uriAuthority != null ? entry.uriAuthority.getHostName() : null, entry.route.pattern, entry.route.handler); } requestRouterCopy = handlerRegistry; } else { if (routeEntries.isEmpty()) { requestRouterCopy = requestRouter; } else { requestRouterCopy = RequestRouter.create( new URIAuthority(actualCanonicalHostName), UriPatternType.URI_PATTERN, routeEntries, RequestRouter.IGNORE_PORT_AUTHORITY_RESOLVER, requestRouter); } } final HandlerFactory handlerFactory; if (!filters.isEmpty()) { final NamedElementChain filterChainDefinition = new NamedElementChain<>(); filterChainDefinition.addLast( new TerminalAsyncServerFilter(new DefaultAsyncResponseExchangeHandlerFactory(requestRouterCopy)), StandardFilter.MAIN_HANDLER.name()); filterChainDefinition.addFirst( new AsyncServerExpectationFilter(), StandardFilter.EXPECT_CONTINUE.name()); for (final FilterEntry entry: filters) { switch (entry.position) { case AFTER: filterChainDefinition.addAfter(entry.existing, entry.filterHandler, entry.name); break; case BEFORE: filterChainDefinition.addBefore(entry.existing, entry.filterHandler, entry.name); break; case REPLACE: filterChainDefinition.replace(entry.existing, entry.filterHandler); break; case FIRST: filterChainDefinition.addFirst(entry.filterHandler, entry.name); break; case LAST: // Don't add last, after TerminalAsyncServerFilter, as that does not delegate to the chain // Instead, add the filter just before it, making it effectively the last filter filterChainDefinition.addBefore(StandardFilter.MAIN_HANDLER.name(), entry.filterHandler, entry.name); break; } } NamedElementChain.Node current = filterChainDefinition.getLast(); AsyncServerFilterChainElement execChain = null; while (current != null) { execChain = new AsyncServerFilterChainElement(current.getValue(), execChain); current = current.getPrevious(); } handlerFactory = new AsyncServerFilterChainExchangeHandlerFactory(execChain, exceptionCallback); } else { handlerFactory = new DefaultAsyncResponseExchangeHandlerFactory(requestRouterCopy, handler -> new BasicAsyncServerExpectationDecorator(handler, exceptionCallback)); } final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory = new ServerH2StreamMultiplexerFactory( httpProcessor != null ? httpProcessor : H2Processors.server(), handlerFactory, h2Config != null ? h2Config : H2Config.DEFAULT, charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT, h2StreamListener, frameFactory); final TlsStrategy actualTlsStrategy = tlsStrategy != null ? tlsStrategy : new H2ServerTlsStrategy(); final ServerHttp1StreamDuplexerFactory http1StreamHandlerFactory = new ServerHttp1StreamDuplexerFactory( httpProcessor != null ? httpProcessor : HttpProcessors.server(), handlerFactory, http1Config != null ? http1Config : Http1Config.DEFAULT, charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT, DefaultConnectionReuseStrategy.INSTANCE, new DefaultHttpRequestParserFactory(http1Config), new DefaultHttpResponseWriterFactory(http1Config), DefaultContentLengthStrategy.INSTANCE, DefaultContentLengthStrategy.INSTANCE, http1StreamListener, exceptionCallback); final IOEventHandlerFactory ioEventHandlerFactory = new ServerHttpProtocolNegotiationStarter( http1StreamHandlerFactory, http2StreamHandlerFactory, versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE, actualTlsStrategy, handshakeTimeout, exceptionCallback); return new HttpAsyncServer(ioEventHandlerFactory, ioReactorConfig, ioSessionDecorator, exceptionCallback, sessionListener, threadPoolListener, null, actualCanonicalHostName); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/package-info.java0100644 0000000 0000000 00000002421 00000250600 030054 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * HTTP/2 capable requester and server bootstrap. */ package org.apache.hc.core5.http2.impl.nio.bootstrap; httpcore5-h2/src/main/java/org/apache/hc/core5/http2/nio/0040755 0000000 0000000 00000000000 00000250600 021713 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/nio/AsyncPingHandler.java0100644 0000000 0000000 00000004151 00000250600 025745 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.nio; import java.io.IOException; import java.nio.ByteBuffer; import org.apache.hc.core5.http.HttpException; /** * Abstract asynchronous ping exchange handler. * * @since 5.0 */ public interface AsyncPingHandler { /** * Returns content of ping message. * * @return the ping message content. */ ByteBuffer getData(); /** * Triggered to signal receipt of a ping response message. * * @param feedback the ping message feedback. * @throws HttpException in case of HTTP protocol violation. * @throws IOException in case of an I/O error. */ void consumeResponse(ByteBuffer feedback) throws HttpException, IOException; /** * Triggered to signal a failure in data processing. * * @param cause the cause of the failure. */ void failed(Exception cause); /** * Triggered to cancel the message exchange. */ void cancel(); } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/nio/package-info.java0100644 0000000 0000000 00000002450 00000250600 025100 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * HTTP/2 transport specific APIs based on the asynchronous, * event driven I/O model. */ package org.apache.hc.core5.http2.nio; httpcore5-h2/src/main/java/org/apache/hc/core5/http2/nio/command/0040755 0000000 0000000 00000000000 00000250600 023331 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/nio/command/PingCommand.java0100644 0000000 0000000 00000003517 00000250600 026373 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.nio.command; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http2.nio.AsyncPingHandler; import org.apache.hc.core5.reactor.Command; import org.apache.hc.core5.util.Args; /** * HTTP/2 ping command. * * @since 5.0 */ @Internal public final class PingCommand implements Command { private final AsyncPingHandler handler; public PingCommand(final AsyncPingHandler handler) { this.handler = Args.notNull(handler, "Handler"); } public AsyncPingHandler getHandler() { return handler; } @Override public boolean cancel() { handler.cancel(); return true; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/nio/command/PushResponseCommand.java0100644 0000000 0000000 00000004003 00000250600 030123 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.nio.command; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.reactor.Command; import org.apache.hc.core5.util.Args; /** * Activates the stream reserved with a push promise * * @since 5.4 */ @Internal public final class PushResponseCommand implements Command { private final int streamId; private final AtomicBoolean cancelled; public PushResponseCommand(final int streamId) { this.streamId = Args.positive(streamId, "Stream Id"); this.cancelled = new AtomicBoolean(); } public int getStreamId() { return streamId; } public boolean isCancelled() { return cancelled.get(); } @Override public boolean cancel() { return cancelled.compareAndSet(false, true); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/nio/command/package-info.java0100644 0000000 0000000 00000002473 00000250600 026523 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * HTTP/2 specific commands for HTTP/2 transport based * on asynchronous, event driven I/O model. */ package org.apache.hc.core5.http2.nio.command; httpcore5-h2/src/main/java/org/apache/hc/core5/http2/nio/pool/0040755 0000000 0000000 00000000000 00000250600 022664 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/nio/pool/H2ConnPool.java0100644 0000000 0000000 00000014502 00000250600 025447 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.nio.pool; import java.net.InetSocketAddress; import java.util.concurrent.Future; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.concurrent.CallbackContribution; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.function.Resolver; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.DefaultAddressResolver; import org.apache.hc.core5.http.nio.command.ShutdownCommand; import org.apache.hc.core5.http.nio.command.StaleCheckCommand; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.AbstractIOSessionPool; import org.apache.hc.core5.reactor.Command; import org.apache.hc.core5.reactor.ConnectionInitiator; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.ssl.TransportSecurityLayer; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; /** * Pool of HTTP/2 message multiplexing capable connections. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE) public final class H2ConnPool extends AbstractIOSessionPool { private final ConnectionInitiator connectionInitiator; private final Resolver addressResolver; private final TlsStrategy tlsStrategy; private volatile TimeValue validateAfterInactivity = TimeValue.NEG_ONE_MILLISECOND; public H2ConnPool( final ConnectionInitiator connectionInitiator, final Resolver addressResolver, final TlsStrategy tlsStrategy) { super(); this.connectionInitiator = Args.notNull(connectionInitiator, "Connection initiator"); this.addressResolver = addressResolver != null ? addressResolver : DefaultAddressResolver.INSTANCE; this.tlsStrategy = tlsStrategy; } public TimeValue getValidateAfterInactivity() { return validateAfterInactivity; } public void setValidateAfterInactivity(final TimeValue timeValue) { this.validateAfterInactivity = timeValue; } @Override protected void closeSession( final IOSession ioSession, final CloseMode closeMode) { if (closeMode == CloseMode.GRACEFUL) { ioSession.enqueue(ShutdownCommand.GRACEFUL, Command.Priority.NORMAL); } else { ioSession.close(closeMode); } } @Override protected Future connectSession( final HttpHost namedEndpoint, final Timeout connectTimeout, final FutureCallback callback) { final InetSocketAddress remoteAddress = addressResolver.resolve(namedEndpoint); return connectionInitiator.connect( namedEndpoint, remoteAddress, null, connectTimeout, null, new CallbackContribution(callback) { @Override public void completed(final IOSession ioSession) { if (tlsStrategy != null && URIScheme.HTTPS.same(namedEndpoint.getSchemeName()) && ioSession instanceof TransportSecurityLayer) { tlsStrategy.upgrade( (TransportSecurityLayer) ioSession, namedEndpoint, null, connectTimeout, new CallbackContribution(callback) { @Override public void completed(final TransportSecurityLayer transportSecurityLayer) { callback.completed(ioSession); } }); ioSession.setSocketTimeout(connectTimeout); } else { callback.completed(ioSession); } } }); } @Override protected void validateSession( final IOSession ioSession, final Callback callback) { if (ioSession.isOpen()) { final TimeValue timeValue = validateAfterInactivity; if (TimeValue.isNonNegative(timeValue)) { final long lastAccessTime = Math.min(ioSession.getLastReadTime(), ioSession.getLastWriteTime()); final long deadline = lastAccessTime + timeValue.toMilliseconds(); if (deadline <= System.currentTimeMillis()) { ioSession.enqueue(new StaleCheckCommand(callback::execute), Command.Priority.NORMAL); return; } } callback.execute(true); } else { callback.execute(false); } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/nio/pool/package-info.java0100644 0000000 0000000 00000002375 00000250600 026057 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * HTTP/2 specific pool component APIs. */ package org.apache.hc.core5.http2.nio.pool; httpcore5-h2/src/main/java/org/apache/hc/core5/http2/nio/support/0040755 0000000 0000000 00000000000 00000250600 023427 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/nio/support/BasicPingHandler.java0100644 0000000 0000000 00000005036 00000250600 027430 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.nio.support; import java.io.IOException; import java.nio.ByteBuffer; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http2.nio.AsyncPingHandler; import org.apache.hc.core5.util.Args; /** * Basic {@link AsyncPingHandler} implementation. * * @since 5.0 */ public class BasicPingHandler implements AsyncPingHandler { private static final byte[] PING_MESSAGE = new byte[] {'*', '*', 'p', 'i', 'n', 'g', '*', '*'}; private final Callback callback; public BasicPingHandler(final Callback callback) { this.callback = Args.notNull(callback, "Callback"); } @Override public ByteBuffer getData() { return ByteBuffer.wrap(PING_MESSAGE); } @Override public void consumeResponse(final ByteBuffer feedback) throws HttpException, IOException { boolean result = true; for (int i = 0; i < PING_MESSAGE.length; i++) { if (!feedback.hasRemaining() || PING_MESSAGE[i] != feedback.get()) { result = false; break; } } callback.execute(result); } @Override public void failed(final Exception cause) { callback.execute(Boolean.FALSE); } @Override public void cancel() { callback.execute(Boolean.FALSE); } } ././@LongLink0100644 0000000 0000000 00000000146 00000250600 011612 Lustar 0000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/nio/support/DefaultAsyncPushConsumerFactory.javahttpcore5-h2/src/main/java/org/apache/hc/core5/http2/nio/support/DefaultAsyncPushConsumerFactory.jav0100644 0000000 0000000 00000005116 00000250600 032417 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.nio.support; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestMapper; import org.apache.hc.core5.http.MisdirectedRequestException; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * Factory for {@link AsyncPushConsumer} instances that make use * of {@link HttpRequestMapper} to dispatch * the request to a particular {@link AsyncPushConsumer} for processing. * * @since 5.0 */ public final class DefaultAsyncPushConsumerFactory implements HandlerFactory { private final HttpRequestMapper> mapper; public DefaultAsyncPushConsumerFactory(final HttpRequestMapper> mapper) { this.mapper = Args.notNull(mapper, "Request handler mapper"); } @Override public AsyncPushConsumer create(final HttpRequest request, final HttpContext context) throws HttpException { try { final Supplier supplier = mapper.resolve(request, context); return supplier != null ? supplier.get() : null; } catch (final MisdirectedRequestException ex) { return null; } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/nio/support/package-info.java0100644 0000000 0000000 00000002416 00000250600 026616 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Support classes for HTTP/2 asynchronous transport. */ package org.apache.hc.core5.http2.nio.support; httpcore5-h2/src/main/java/org/apache/hc/core5/http2/priority/0040755 0000000 0000000 00000000000 00000250600 023007 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/priority/PriorityFormatter.java0100644 0000000 0000000 00000006021 00000250600 027353 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.priority; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BufferedHeader; import org.apache.hc.core5.util.CharArrayBuffer; /** * Formats PriorityValue as RFC 9218 Structured Fields Dictionary. * Only emits non-defaults: u when != 3, i when true. * Returns null when both are defaults (callers should omit the header then). */ @Internal public final class PriorityFormatter { private PriorityFormatter() { } public static void format(final CharArrayBuffer dst, final PriorityValue value) { if (value == null) { return; } boolean urgencyPresent = false; if (value.getUrgency() != PriorityValue.DEFAULT_URGENCY) { dst.append("u="); dst.append(value.getUrgency()); urgencyPresent = true; } if (value.isIncremental()) { // In SF Dictionary, boolean true can be represented by key without value (per RFC 8941). if (urgencyPresent) { dst.append(", "); } dst.append("i"); } } public static String format(final PriorityValue value) { if (value == null) { return null; } final CharArrayBuffer buf = new CharArrayBuffer(16); format(buf, value); return buf.toString(); } public static Header formatHeader(final PriorityValue value) { if (value == null) { return new BasicHeader(HttpHeaders.PRIORITY, null); } final CharArrayBuffer buf = new CharArrayBuffer(16); buf.append(HttpHeaders.PRIORITY); buf.append(": "); format(buf, value); return BufferedHeader.create(buf); } }httpcore5-h2/src/main/java/org/apache/hc/core5/http2/priority/PriorityMerge.java0100644 0000000 0000000 00000004144 00000250600 026453 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.priority; import org.apache.hc.core5.annotation.Internal; /** * Non-normative merge helper per RFC 9218 §8 example. * Policy: if server provides a member, prefer it; otherwise keep client's. */ @Internal public final class PriorityMerge { private PriorityMerge() { } public static PriorityValue merge(final PriorityValue clientRequest, final PriorityParams serverResponse) { final int u = serverResponse != null && serverResponse.getUrgency() != null ? serverResponse.getUrgency() : (clientRequest != null ? clientRequest.getUrgency() : PriorityValue.DEFAULT_URGENCY); final boolean i = serverResponse != null && serverResponse.getIncremental() != null ? serverResponse.getIncremental() : (clientRequest != null ? clientRequest.isIncremental() : PriorityValue.DEFAULT_INCREMENTAL); return PriorityValue.of(u, i); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/priority/PriorityParams.java0100644 0000000 0000000 00000004464 00000250600 026644 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.priority; import org.apache.hc.core5.annotation.Internal; /** * Presence-aware view for RFC 9218 Priority on responses. */ @Internal public final class PriorityParams { private final Integer urgency; // null if 'u' absent private final Boolean incremental; // null if 'i' absent public PriorityParams(final Integer urgency, final Boolean incremental) { if (urgency != null && (urgency < 0 || urgency > 7)) { throw new IllegalArgumentException("urgency out of range [0..7]: " + urgency); } this.urgency = urgency; this.incremental = incremental; } public Integer getUrgency() { return urgency; } public Boolean getIncremental() { return incremental; } /** * Convert to concrete value by applying RFC defaults (u=3, i=false) for missing members. */ public PriorityValue toValueWithDefaults() { final int u = urgency != null ? urgency : PriorityValue.DEFAULT_URGENCY; final boolean i = incremental != null ? incremental : PriorityValue.DEFAULT_INCREMENTAL; return PriorityValue.of(u, i); } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/priority/PriorityParamsParser.java0100644 0000000 0000000 00000013321 00000250600 030011 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.priority; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.http.message.ParserCursor; import org.apache.hc.core5.util.Tokenizer; @Internal public final class PriorityParamsParser { private static final Tokenizer TK = Tokenizer.INSTANCE; private static final Tokenizer.Delimiter KEY_DELIMS = Tokenizer.delimiters('=', ',', ';'); private static final Tokenizer.Delimiter VALUE_DELIMS = Tokenizer.delimiters(',', ';'); private PriorityParamsParser() { } public static PriorityParams parse(final CharSequence src, final ParserCursor c) { Integer u = null; Boolean i = null; while (!c.atEnd()) { TK.skipWhiteSpace(src, c); if (c.atEnd()) { break; } final String k = TK.parseToken(src, c, KEY_DELIMS); if (k == null || k.isEmpty()) { skipToNextItem(src, c); continue; } TK.skipWhiteSpace(src, c); final char ch = c.atEnd() ? 0 : src.charAt(c.getPos()); if (ch == '=') { c.updatePos(c.getPos() + 1); TK.skipWhiteSpace(src, c); if ("u".equalsIgnoreCase(k)) { final String t = TK.parseToken(src, c, VALUE_DELIMS); try { final int v = Integer.parseInt(t); if (v >= 0 && v <= 7) { u = v; } } catch (final Exception ignore) { } } else if ("i".equalsIgnoreCase(k)) { final char b = c.atEnd() ? 0 : src.charAt(c.getPos()); if (b == '?') { c.updatePos(c.getPos() + 1); final char v = c.atEnd() ? 0 : src.charAt(c.getPos()); if (v == '1') { i = Boolean.TRUE; c.updatePos(c.getPos() + 1); } else if (v == '0') { i = Boolean.FALSE; c.updatePos(c.getPos() + 1); } } else { final String t = TK.parseToken(src, c, VALUE_DELIMS); if ("1".equals(t)) { i = Boolean.TRUE; } else if ("0".equals(t)) { i = Boolean.FALSE; } } } else { TK.parseToken(src, c, VALUE_DELIMS); } skipParamsThenNextItem(src, c); } else { if ("i".equalsIgnoreCase(k)) { i = Boolean.TRUE; } skipParamsThenNextItem(src, c); } } return new PriorityParams(u, i); } public static PriorityParams parse(final String value) { if (value == null || value.isEmpty()) { return new PriorityParams(null, null); } final ParserCursor c = new ParserCursor(0, value.length()); return parse(value, c); } public static PriorityParams parse(final Header header) { if (header == null) { return new PriorityParams(null, null); } final PriorityParams[] box = new PriorityParams[1]; MessageSupport.parseHeader(header, (seq, cur) -> box[0] = parse(seq, cur)); return box[0] != null ? box[0] : new PriorityParams(null, null); } private static void skipToNextItem(final CharSequence buf, final ParserCursor c) { while (!c.atEnd()) { final char ch = buf.charAt(c.getPos()); c.updatePos(c.getPos() + 1); if (ch == ',') { break; } } } private static void skipParamsThenNextItem(final CharSequence buf, final ParserCursor c) { while (!c.atEnd()) { final int pos = c.getPos(); final char ch = buf.charAt(pos); if (ch == ';') { c.updatePos(pos + 1); TK.parseToken(buf, c, VALUE_DELIMS); continue; } break; } while (!c.atEnd()) { final char ch = buf.charAt(c.getPos()); if (ch == ',') { c.updatePos(c.getPos() + 1); break; } c.updatePos(c.getPos() + 1); } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/priority/PriorityParser.java0100644 0000000 0000000 00000015051 00000250600 026647 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.priority; import java.util.Locale; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.http.message.ParserCursor; import org.apache.hc.core5.util.Tokenizer; @Internal public final class PriorityParser { private static final Tokenizer TK = Tokenizer.INSTANCE; // Non-deprecated delimiter predicates private static final Tokenizer.Delimiter KEY_DELIMS = Tokenizer.delimiters('=', ',', ';'); private static final Tokenizer.Delimiter VALUE_DELIMS = Tokenizer.delimiters(',', ';'); private PriorityParser() { } public static PriorityValue parse(final Header header) { if (header == null) { return PriorityValue.defaults(); } final PriorityValue[] out = new PriorityValue[1]; MessageSupport.parseHeader(header, (seq, cur) -> out[0] = parse(seq, cur)); return out[0] != null ? out[0] : PriorityValue.defaults(); } public static PriorityValue parse(final String headerValue) { if (headerValue == null || headerValue.isEmpty()) { return PriorityValue.defaults(); } final ParserCursor c = new ParserCursor(0, headerValue.length()); return parse(headerValue, c); } public static PriorityValue parse(final CharSequence src, final ParserCursor cursor) { int urgency = PriorityValue.DEFAULT_URGENCY; boolean incremental = PriorityValue.DEFAULT_INCREMENTAL; while (!cursor.atEnd()) { TK.skipWhiteSpace(src, cursor); if (cursor.atEnd()) { break; } final String rawKey = TK.parseToken(src, cursor, KEY_DELIMS); if (rawKey == null || rawKey.isEmpty()) { skipToNextItem(src, cursor); continue; } final String key = rawKey.toLowerCase(Locale.ROOT); TK.skipWhiteSpace(src, cursor); final char ch = currentChar(src, cursor); if (ch == '=') { cursor.updatePos(cursor.getPos() + 1); TK.skipWhiteSpace(src, cursor); if ("u".equals(key)) { final String numTok = TK.parseToken(src, cursor, VALUE_DELIMS); final Integer u = safeParseInt(numTok); if (u != null && u >= 0 && u <= 7) { urgency = u; } } else if ("i".equals(key)) { final char b = currentChar(src, cursor); if (b == '?') { cursor.updatePos(cursor.getPos() + 1); final char v = currentChar(src, cursor); if (v == '1') { incremental = true; cursor.updatePos(cursor.getPos() + 1); } else if (v == '0') { incremental = false; cursor.updatePos(cursor.getPos() + 1); } } else { final String tok = TK.parseToken(src, cursor, VALUE_DELIMS); if ("1".equals(tok)) { incremental = true; } else if ("0".equals(tok)) { incremental = false; } } } else { TK.parseToken(src, cursor, VALUE_DELIMS); // ignore unknown member } skipParamsThenNextItem(src, cursor); } else { if ("i".equals(key)) { incremental = true; // bare true } skipParamsThenNextItem(src, cursor); } } return PriorityValue.of(urgency, incremental); } private static char currentChar(final CharSequence buf, final Tokenizer.Cursor c) { return c.atEnd() ? 0 : buf.charAt(c.getPos()); } private static Integer safeParseInt(final String s) { if (s == null) { return null; } try { return Integer.parseInt(s); } catch (final NumberFormatException ignore) { return null; } } private static void skipToNextItem(final CharSequence buf, final Tokenizer.Cursor c) { while (!c.atEnd()) { final char ch = buf.charAt(c.getPos()); c.updatePos(c.getPos() + 1); if (ch == ',') { break; } } } // Skip any SF parameters (';param[=value]...'), then advance to the next item (after a single ',') if present. private static void skipParamsThenNextItem(final CharSequence buf, final Tokenizer.Cursor c) { while (!c.atEnd()) { final int pos = c.getPos(); final char ch = buf.charAt(pos); if (ch == ';') { c.updatePos(pos + 1); // consume parameter token (up to ',' or ';') TK.parseToken(buf, c, VALUE_DELIMS); continue; } break; } while (!c.atEnd()) { final char ch = buf.charAt(c.getPos()); if (ch == ',') { c.updatePos(c.getPos() + 1); // consume comma break; } c.updatePos(c.getPos() + 1); } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/priority/PriorityValue.java0100644 0000000 0000000 00000005714 00000250600 026474 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.priority; import java.util.Objects; public final class PriorityValue { public static final int DEFAULT_URGENCY = 3; public static final boolean DEFAULT_INCREMENTAL = false; private final int urgency; private final boolean incremental; public PriorityValue(final int urgency, final boolean incremental) { if (urgency < 0 || urgency > 7) { throw new IllegalArgumentException("urgency out of range [0..7]: " + urgency); } this.urgency = urgency; this.incremental = incremental; } public static PriorityValue of(final int urgency, final boolean incremental) { return new PriorityValue(urgency, incremental); } public static PriorityValue defaults() { return new PriorityValue(DEFAULT_URGENCY, DEFAULT_INCREMENTAL); } public int getUrgency() { return urgency; } public boolean isIncremental() { return incremental; } public PriorityValue withUrgency(final int newUrgency) { return new PriorityValue(newUrgency, this.incremental); } public PriorityValue withIncremental(final boolean newIncremental) { return new PriorityValue(this.urgency, newIncremental); } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (!(obj instanceof PriorityValue)) { return false; } final PriorityValue other = (PriorityValue) obj; return urgency == other.urgency && incremental == other.incremental; } @Override public int hashCode() { return Objects.hash(urgency, incremental); } @Override public String toString() { return "PriorityValue{u=" + urgency + ", i=" + incremental + '}'; } }httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/0040755 0000000 0000000 00000000000 00000250600 022767 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestConformance.java0100644 0000000 0000000 00000007152 00000250600 027631 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * This request interceptor is responsible for execution of the protocol conformance * checks on incoming or outgoing HTTP/2 request messages. * * @since 5.4 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class H2RequestConformance implements HttpRequestInterceptor { public static final H2RequestConformance INSTANCE = new H2RequestConformance(); private final String[] illegalHeaderNames; @Internal public H2RequestConformance(final String... illegalHeaderNames) { super(); this.illegalHeaderNames = illegalHeaderNames; } public H2RequestConformance() { this( HttpHeaders.CONNECTION, HttpHeaders.KEEP_ALIVE, HttpHeaders.PROXY_CONNECTION, HttpHeaders.TRANSFER_ENCODING, HttpHeaders.UPGRADE, HttpHeaders.TE); } @Override public void process(final HttpRequest request, final EntityDetails entity, final HttpContext localContext) throws HttpException, IOException { Args.notNull(request, "HTTP request"); for (int i = 0; i < illegalHeaderNames.length; i++) { final String headerName = illegalHeaderNames[i]; final Header header = request.getFirstHeader(headerName); if (header != null) { if (headerName.equalsIgnoreCase(HttpHeaders.TE)) { final String value = header.getValue(); if (!"trailers".equalsIgnoreCase(value)) { throw new ProtocolException("Header '%s: %s' is illegal for HTTP/2 messages", HttpHeaders.TE, value); } } else { throw new ProtocolException("Header '%s' is illegal for HTTP/2 messages", headerName); } } } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestConnControl.java0100644 0000000 0000000 00000005112 00000250600 027627 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.RequestConnControl; import org.apache.hc.core5.util.Args; /** * HTTP/2 compatible extension of {@link RequestConnControl}. *

* This interceptor is recommended for the HTTP protocol conformance and * the correct operation of the client-side message processing pipeline. *

* * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class H2RequestConnControl extends RequestConnControl { /** * Singleton instance. * * @since 5.2 */ public static final H2RequestConnControl INSTANCE = new H2RequestConnControl(); @Override public void process( final HttpRequest request, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { Args.notNull(context, "HTTP context"); final ProtocolVersion ver = context.getProtocolVersion(); if (ver.getMajor() < 2) { super.process(request, entity, context); } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestContent.java0100644 0000000 0000000 00000006614 00000250600 027013 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.RequestContent; import org.apache.hc.core5.util.Args; /** * HTTP/2 compatible extension of {@link RequestContent}. *

* This interceptor is essential for the HTTP protocol conformance and * the correct operation of the client-side message processing pipeline. *

* * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class H2RequestContent extends RequestContent { /** * Singleton instance. * * @since 5.2 */ public static final H2RequestContent INSTANCE = new H2RequestContent(); public H2RequestContent() { super(); } public H2RequestContent(final boolean overwrite) { super(overwrite); } @Override public void process( final HttpRequest request, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { Args.notNull(context, "HTTP context"); final ProtocolVersion ver = context.getProtocolVersion(); if (ver.getMajor() < 2) { super.process(request, entity, context); } else if (entity != null) { final String method = request.getMethod(); if (Method.TRACE.isSame(method)) { throw new ProtocolException("TRACE request may not enclose an entity"); } MessageSupport.addContentTypeHeader(request, entity); MessageSupport.addContentEncodingHeader(request, entity); MessageSupport.addTrailerHeader(request, entity); // Check for OPTIONS request with content but no Content-Type header validateOptionsContentType(request); } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestTargetHost.java0100644 0000000 0000000 00000005102 00000250600 027454 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.RequestTargetHost; import org.apache.hc.core5.util.Args; /** * HTTP/2 compatible extension of {@link RequestTargetHost}. *

* This interceptor is essential for the HTTP protocol conformance and * the correct operation of the client-side message processing pipeline. *

* * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class H2RequestTargetHost extends RequestTargetHost { /** * Singleton instance. * * @since 5.2 */ public static final H2RequestTargetHost INSTANCE = new H2RequestTargetHost(); @Override public void process( final HttpRequest request, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { Args.notNull(context, "HTTP context"); final ProtocolVersion ver = context.getProtocolVersion(); if (ver.getMajor() < 2) { super.process(request, entity, context); } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestValidateHost.java0100644 0000000 0000000 00000005116 00000250600 027764 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.RequestValidateHost; import org.apache.hc.core5.util.Args; /** * HTTP/2 compatible extension of {@link RequestValidateHost}. *

* This interceptor is essential for the HTTP protocol conformance and * the correct operation of the server-side message processing pipeline. *

* * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class H2RequestValidateHost extends RequestValidateHost { /** * Singleton instance. * * @since 5.2 */ public static final H2RequestValidateHost INSTANCE = new H2RequestValidateHost(); @Override public void process( final HttpRequest request, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { Args.notNull(context, "HTTP context"); final ProtocolVersion ver = context.getProtocolVersion(); if (ver.getMajor() < 2) { super.process(request, entity, context); } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2ResponseConformance.java0100644 0000000 0000000 00000006237 00000250600 030002 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponseInterceptor; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; /** * This response interceptor is responsible for making the protocol conformance checks * of incoming or outgoing HTTP/2 response messages. * * @since 5.4 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class H2ResponseConformance implements HttpResponseInterceptor { public static final H2ResponseConformance INSTANCE = new H2ResponseConformance(); private final String[] illegalHeaderNames; @Internal public H2ResponseConformance(final String... illegalHeaderNames) { super(); this.illegalHeaderNames = illegalHeaderNames; } public H2ResponseConformance() { this( HttpHeaders.CONNECTION, HttpHeaders.KEEP_ALIVE, HttpHeaders.TRANSFER_ENCODING, HttpHeaders.UPGRADE); } @Override public void process(final HttpResponse response, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { Args.notNull(response, "HTTP response"); for (int i = 0; i < illegalHeaderNames.length; i++) { final String headerName = illegalHeaderNames[i]; final Header header = response.getFirstHeader(headerName); if (header != null) { throw new ProtocolException("Header '%s' is illegal for HTTP/2 messages", headerName); } } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2ResponseConnControl.java0100644 0000000 0000000 00000005123 00000250600 027777 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.ResponseConnControl; import org.apache.hc.core5.util.Args; /** * HTTP/2 compatible extension of {@link ResponseConnControl}. *

* This interceptor is recommended for the HTTP protocol conformance and * the correct operation of the server-side message processing pipeline. *

* * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class H2ResponseConnControl extends ResponseConnControl { /** * Singleton instance. * * @since 5.2 */ public static final H2ResponseConnControl INSTANCE = new H2ResponseConnControl(); @Override public void process( final HttpResponse response, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { Args.notNull(context, "HTTP context"); final ProtocolVersion ver = context.getProtocolVersion(); if (ver.getMajor() < 2) { super.process(response, entity, context); } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2ResponseContent.java0100644 0000000 0000000 00000005762 00000250600 027164 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.protocol; import java.io.IOException; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.ResponseContent; import org.apache.hc.core5.util.Args; /** * HTTP/2 compatible extension of {@link ResponseContent}. *

* This interceptor is essential for the HTTP protocol conformance and * the correct operation of the server-side message processing pipeline. *

* * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class H2ResponseContent extends ResponseContent { /** * Singleton instance. * * @since 5.2 */ public static final H2ResponseContent INSTANCE = new H2ResponseContent(); public H2ResponseContent() { super(); } public H2ResponseContent(final boolean overwrite) { super(overwrite); } @Override public void process( final HttpResponse response, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { Args.notNull(context, "HTTP context"); final ProtocolVersion ver = context.getProtocolVersion(); if (ver.getMajor() < 2) { super.process(response, entity, context); } else if (entity != null) { MessageSupport.addContentTypeHeader(response, entity); MessageSupport.addContentEncodingHeader(response, entity); MessageSupport.addTrailerHeader(response, entity); } } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/package-info.java0100644 0000000 0000000 00000002366 00000250600 026162 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * HTTP/2 protocol interceptors. */ package org.apache.hc.core5.http2.protocol; httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/0040755 0000000 0000000 00000000000 00000250600 021727 5ustar000000000 0000000 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/ApplicationProtocol.java0100644 0000000 0000000 00000003253 00000250600 026557 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.ssl; /** * Supported application protocols. * * @since 5.0 */ public enum ApplicationProtocol { /** * The HTTP/2 application protocol. */ HTTP_2("h2"), /** * The HTTP/1.1 application protocol. */ HTTP_1_1("http/1.1"); /** * The application protocol ID. */ public final String id; ApplicationProtocol(final String id) { this.id = id; } @Override public String toString() { return id; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/ConscryptClientTlsStrategy.java0100644 0000000 0000000 00000012442 00000250600 030123 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.ssl; import java.net.SocketAddress; import javax.net.ssl.SSLContext; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.reactor.ssl.SSLBufferMode; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; import org.apache.hc.core5.reactor.ssl.TransportSecurityLayer; import org.apache.hc.core5.ssl.SSLContexts; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; /** * Conscrypt specific client-side implementation of {@link TlsStrategy} that upgrades outbound * connections with TLS security. This strategy will try to negotiate an application protocol * using TLS ALPN extension support through Conscrypt native APIs based on * {@link org.apache.hc.core5.http2.HttpVersionPolicy} passed to the strategy as an attachment. * * @since 5.0 */ public class ConscryptClientTlsStrategy implements TlsStrategy { private final SSLContext sslContext; private final SSLBufferMode sslBufferMode; private final SSLSessionInitializer initializer; private final SSLSessionVerifier verifier; public ConscryptClientTlsStrategy( final SSLContext sslContext, final SSLBufferMode sslBufferMode, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier) { this.sslContext = Args.notNull(sslContext, "SSL context"); this.sslBufferMode = sslBufferMode; this.initializer = initializer; this.verifier = verifier; } public ConscryptClientTlsStrategy( final SSLContext sslContext, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier) { this(sslContext, null, initializer, verifier); } public ConscryptClientTlsStrategy( final SSLContext sslContext, final SSLSessionVerifier verifier) { this(sslContext, null, null, verifier); } public ConscryptClientTlsStrategy(final SSLContext sslContext) { this(sslContext, null, null, null); } /** * Empty constructor with the default SSL context based on system properties. * @see SSLContext * @since 5.2 */ public ConscryptClientTlsStrategy() { this(SSLContexts.createSystemDefault(), null, null, null); } /** * Constructor with the default SSL context based on system properties and custom {@link SSLSessionVerifier} verifier. * @param verifier the custom {@link SSLSessionVerifier}. * @since 5.2 */ public ConscryptClientTlsStrategy(final SSLSessionVerifier verifier) { this(SSLContexts.createSystemDefault(), verifier); } @Override public void upgrade( final TransportSecurityLayer tlsSession, final NamedEndpoint endpoint, final Object attachment, final Timeout handshakeTimeout, final FutureCallback callback) { tlsSession.startTls( sslContext, endpoint, sslBufferMode, ConscryptSupport.initialize(attachment, initializer), ConscryptSupport.verify(verifier), handshakeTimeout, callback); } /** * @deprecated use {@link #upgrade(TransportSecurityLayer, NamedEndpoint, Object, Timeout, FutureCallback)} */ @Deprecated @Override public boolean upgrade( final TransportSecurityLayer tlsSession, final HttpHost host, final SocketAddress localAddress, final SocketAddress remoteAddress, final Object attachment, final Timeout handshakeTimeout) { final String scheme = host != null ? host.getSchemeName() : null; if (URIScheme.HTTPS.same(scheme)) { upgrade(tlsSession, host, attachment, handshakeTimeout, null); return true; } return false; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/ConscryptServerTlsStrategy.java0100644 0000000 0000000 00000017704 00000250600 030161 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.ssl; import java.net.SocketAddress; import javax.net.ssl.SSLContext; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.reactor.ssl.SSLBufferMode; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; import org.apache.hc.core5.reactor.ssl.TransportSecurityLayer; import org.apache.hc.core5.ssl.SSLContexts; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; /** * Conscrypt specific server-side implementation of {@link TlsStrategy} that upgrades inbound * connection with TLS security. This strategy will try to negotiate an application protocol * using TLS ALPN extension support through Conscrypt native APIs based on * {@link org.apache.hc.core5.http2.HttpVersionPolicy} passed to the strategy as an attachment. * * @since 5.0 */ public class ConscryptServerTlsStrategy implements TlsStrategy { private final SSLContext sslContext; @SuppressWarnings("deprecation") private final org.apache.hc.core5.http.nio.ssl.SecurePortStrategy securePortStrategy; private final SSLBufferMode sslBufferMode; private final SSLSessionInitializer initializer; private final SSLSessionVerifier verifier; /** * @deprecated Use {@link ConscryptServerTlsStrategy#ConscryptServerTlsStrategy(SSLContext, SSLBufferMode, SSLSessionInitializer, SSLSessionVerifier)} */ @Deprecated public ConscryptServerTlsStrategy( final SSLContext sslContext, final org.apache.hc.core5.http.nio.ssl.SecurePortStrategy securePortStrategy, final SSLBufferMode sslBufferMode, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier) { this.sslContext = Args.notNull(sslContext, "SSL context"); this.securePortStrategy = securePortStrategy; this.sslBufferMode = sslBufferMode; this.initializer = initializer; this.verifier = verifier; } /** * @deprecated Use {@link ConscryptServerTlsStrategy#ConscryptServerTlsStrategy(SSLContext, SSLSessionInitializer, SSLSessionVerifier)} */ @Deprecated public ConscryptServerTlsStrategy( final SSLContext sslContext, final org.apache.hc.core5.http.nio.ssl.SecurePortStrategy securePortStrategy, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier) { this(sslContext, securePortStrategy, null, initializer, verifier); } /** * @deprecated Use {@link ConscryptServerTlsStrategy#ConscryptServerTlsStrategy(SSLContext, SSLSessionVerifier)} */ @Deprecated public ConscryptServerTlsStrategy( final SSLContext sslContext, final org.apache.hc.core5.http.nio.ssl.SecurePortStrategy securePortStrategy, final SSLSessionVerifier verifier) { this(sslContext, securePortStrategy, null, null, verifier); } /** * @deprecated Use {@link ConscryptServerTlsStrategy#ConscryptServerTlsStrategy(SSLContext)} */ @Deprecated public ConscryptServerTlsStrategy(final SSLContext sslContext, final org.apache.hc.core5.http.nio.ssl.SecurePortStrategy securePortStrategy) { this(sslContext, securePortStrategy, null, null, null); } /** * @deprecated Use {@link ConscryptServerTlsStrategy#ConscryptServerTlsStrategy(SSLContext)} */ @Deprecated public ConscryptServerTlsStrategy(final SSLContext sslContext, final int... securePorts) { this(sslContext, new org.apache.hc.core5.http.nio.ssl.FixedPortStrategy(securePorts)); } public ConscryptServerTlsStrategy( final SSLContext sslContext, final SSLBufferMode sslBufferMode, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier) { this.sslContext = Args.notNull(sslContext, "SSL context"); this.sslBufferMode = sslBufferMode; this.initializer = initializer; this.verifier = verifier; this.securePortStrategy = null; } public ConscryptServerTlsStrategy( final SSLContext sslContext, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier) { this(sslContext, (SSLBufferMode) null, initializer, verifier); } public ConscryptServerTlsStrategy(final SSLContext sslContext, final SSLSessionVerifier verifier) { this(sslContext, (SSLBufferMode) null, null, verifier); } public ConscryptServerTlsStrategy(final SSLContext sslContext) { this(sslContext, (SSLBufferMode) null, null, null); } /** * Empty constructor with the default SSL context based on system properties. * @see SSLContext * @since 5.2 */ public ConscryptServerTlsStrategy() { this(SSLContexts.createSystemDefault(), (SSLBufferMode) null, null, null); } /** * Constructor with the default SSL context based on system properties and custom {@link SSLSessionVerifier}. * @param verifier the custom {@link SSLSessionVerifier}. * @see SSLContext * @since 5.2 */ public ConscryptServerTlsStrategy(final SSLSessionVerifier verifier) { this(SSLContexts.createSystemDefault(), (SSLBufferMode) null, null, verifier); } private boolean isApplicable(final SocketAddress localAddress) { return securePortStrategy == null || securePortStrategy.isSecure(localAddress); } @Override public void upgrade( final TransportSecurityLayer tlsSession, final NamedEndpoint endpoint, final Object attachment, final Timeout handshakeTimeout, final FutureCallback callback) { tlsSession.startTls( sslContext, endpoint, sslBufferMode, ConscryptSupport.initialize(attachment, initializer), ConscryptSupport.verify(verifier), handshakeTimeout, callback); } /** * @deprecated use {@link #upgrade(TransportSecurityLayer, NamedEndpoint, Object, Timeout, FutureCallback)} */ @Deprecated @Override public boolean upgrade( final TransportSecurityLayer tlsSession, final HttpHost host, final SocketAddress localAddress, final SocketAddress remoteAddress, final Object attachment, final Timeout handshakeTimeout) { if (isApplicable(localAddress)) { upgrade(tlsSession, host, attachment, handshakeTimeout, null); return true; } return false; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/ConscryptSupport.java0100644 0000000 0000000 00000006247 00000250600 026161 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.ssl; import javax.net.ssl.SSLParameters; import org.apache.hc.core5.http.ssl.TLS; import org.apache.hc.core5.http.ssl.TlsCiphers; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; import org.apache.hc.core5.reactor.ssl.TlsDetails; import org.conscrypt.Conscrypt; /** * Conscrypt TLS support methods * * @since 5.0 */ public final class ConscryptSupport { public static SSLSessionInitializer initialize( final Object attachment, final SSLSessionInitializer initializer) { return (endpoint, sslEngine) -> { final SSLParameters sslParameters = sslEngine.getSSLParameters(); sslParameters.setProtocols(TLS.excludeWeak(sslParameters.getProtocols())); sslParameters.setCipherSuites(TlsCiphers.excludeH2Blacklisted(sslParameters.getCipherSuites())); H2TlsSupport.setEnableRetransmissions(sslParameters, false); final String[] appProtocols = H2TlsSupport.selectApplicationProtocols(attachment); if (Conscrypt.isConscrypt(sslEngine)) { sslEngine.setSSLParameters(sslParameters); Conscrypt.setApplicationProtocols(sslEngine, appProtocols); } else { sslParameters.setApplicationProtocols(appProtocols); sslEngine.setSSLParameters(sslParameters); } if (initializer != null) { initializer.initialize(endpoint, sslEngine); } }; } public static SSLSessionVerifier verify(final SSLSessionVerifier verifier) { return (endpoint, sslEngine) -> { TlsDetails tlsDetails = verifier != null ? verifier.verify(endpoint, sslEngine) : null; if (tlsDetails == null && Conscrypt.isConscrypt(sslEngine)) { tlsDetails = new TlsDetails(sslEngine.getSession(), Conscrypt.getApplicationProtocol(sslEngine)); } return tlsDetails; }; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/H2ClientTlsStrategy.java0100644 0000000 0000000 00000013100 00000250600 026400 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.ssl; import java.net.SocketAddress; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.reactor.ssl.SSLBufferMode; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; import org.apache.hc.core5.reactor.ssl.TransportSecurityLayer; import org.apache.hc.core5.ssl.SSLContexts; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; /** * Default client-side implementation of {@link TlsStrategy} that upgrades outbound connections * with TLS security. This strategy will try to negotiate an application protocol using * TLS ALPN extension based on {@link org.apache.hc.core5.http2.HttpVersionPolicy} passed to * the strategy as an attachment. The strategy will also enforce restrictions on TLS parameters * required by the HTTP/2 specification. * * @since 5.0 */ public class H2ClientTlsStrategy implements TlsStrategy { private final SSLContext sslContext; private final SSLBufferMode sslBufferMode; private final SSLSessionInitializer initializer; private final SSLSessionVerifier verifier; public H2ClientTlsStrategy( final SSLContext sslContext, final SSLBufferMode sslBufferMode, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier) { this.sslContext = Args.notNull(sslContext, "SSL context"); this.sslBufferMode = sslBufferMode; this.initializer = initializer; this.verifier = verifier; } public H2ClientTlsStrategy( final SSLContext sslContext, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier) { this(sslContext, null, initializer, verifier); } public H2ClientTlsStrategy( final SSLContext sslContext, final SSLSessionVerifier verifier) { this(sslContext, null, null, verifier); } public H2ClientTlsStrategy(final SSLContext sslContext) { this(sslContext, null, null, null); } public H2ClientTlsStrategy() { this(SSLContexts.createSystemDefault()); } /** * Constructor with the default SSL context based on system properties and custom {@link SSLSessionVerifier}. * @param verifier the custom {@link SSLSessionVerifier}. * @see SSLContext * @since 5.2 */ public H2ClientTlsStrategy( final SSLSessionVerifier verifier) { this(SSLContexts.createSystemDefault(), null, null, verifier); } @Override public void upgrade( final TransportSecurityLayer tlsSession, final NamedEndpoint endpoint, final Object attachment, final Timeout handshakeTimeout, final FutureCallback callback) { tlsSession.startTls( sslContext, endpoint, sslBufferMode, (e, sslEngine) -> { final SSLParameters sslParameters = sslEngine.getSSLParameters(); sslParameters.setEndpointIdentificationAlgorithm(URIScheme.HTTPS.id); sslEngine.setSSLParameters(H2TlsSupport.enforceRequirements(attachment, sslParameters)); if (initializer != null) { initializer.initialize(e, sslEngine); } }, verifier, handshakeTimeout, callback); } /** * @deprecated use {@link #upgrade(TransportSecurityLayer, NamedEndpoint, Object, Timeout, FutureCallback)} */ @Deprecated @Override public boolean upgrade( final TransportSecurityLayer tlsSession, final HttpHost host, final SocketAddress localAddress, final SocketAddress remoteAddress, final Object attachment, final Timeout handshakeTimeout) { final String scheme = host != null ? host.getSchemeName() : null; if (URIScheme.HTTPS.same(scheme)) { upgrade(tlsSession, host, attachment, handshakeTimeout, null); return true; } return false; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/H2ServerTlsStrategy.java0100644 0000000 0000000 00000016375 00000250600 026451 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.ssl; import java.net.SocketAddress; import javax.net.ssl.SSLContext; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.reactor.ssl.SSLBufferMode; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; import org.apache.hc.core5.reactor.ssl.TransportSecurityLayer; import org.apache.hc.core5.ssl.SSLContexts; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; /** * Default server-side implementation of {@link TlsStrategy} that upgrades inbound connections * with TLS security. This strategy will try to negotiate an application protocol * using TLS ALPN extension based on {@link org.apache.hc.core5.http2.HttpVersionPolicy} * passed to the strategy as an attachment. The strategy will also enforce restrictions on * TLS parameters required by the HTTP/2 specification. * * @since 5.0 */ public class H2ServerTlsStrategy implements TlsStrategy { private final SSLContext sslContext; @SuppressWarnings("deprecation") private final org.apache.hc.core5.http.nio.ssl.SecurePortStrategy securePortStrategy; private final SSLBufferMode sslBufferMode; private final SSLSessionInitializer initializer; private final SSLSessionVerifier verifier; /** * @deprecated Use {@link H2ServerTlsStrategy#H2ServerTlsStrategy(SSLContext, SSLBufferMode, SSLSessionInitializer, SSLSessionVerifier)} */ @Deprecated public H2ServerTlsStrategy( final SSLContext sslContext, final org.apache.hc.core5.http.nio.ssl.SecurePortStrategy securePortStrategy, final SSLBufferMode sslBufferMode, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier) { this.sslContext = Args.notNull(sslContext, "SSL context"); this.securePortStrategy = securePortStrategy; this.sslBufferMode = sslBufferMode; this.initializer = initializer; this.verifier = verifier; } /** * @deprecated Use {@link H2ServerTlsStrategy#H2ServerTlsStrategy(SSLContext, SSLSessionInitializer, SSLSessionVerifier)} */ @Deprecated public H2ServerTlsStrategy( final SSLContext sslContext, final org.apache.hc.core5.http.nio.ssl.SecurePortStrategy securePortStrategy, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier) { this(sslContext, securePortStrategy, null, initializer, verifier); } /** * @deprecated Use {@link H2ServerTlsStrategy#H2ServerTlsStrategy(SSLContext, SSLSessionVerifier)} */ @Deprecated public H2ServerTlsStrategy( final SSLContext sslContext, final org.apache.hc.core5.http.nio.ssl.SecurePortStrategy securePortStrategy, final SSLSessionVerifier verifier) { this(sslContext, securePortStrategy, null, null, verifier); } /** * @deprecated Use {@link H2ServerTlsStrategy#H2ServerTlsStrategy(SSLContext)} */ @Deprecated public H2ServerTlsStrategy(final SSLContext sslContext, final org.apache.hc.core5.http.nio.ssl.SecurePortStrategy securePortStrategy) { this(sslContext, securePortStrategy, null, null, null); } /** * @deprecated Use {@link H2ServerTlsStrategy#H2ServerTlsStrategy()} */ @Deprecated public H2ServerTlsStrategy(final int... securePorts) { this(SSLContexts.createSystemDefault(), new org.apache.hc.core5.http.nio.ssl.FixedPortStrategy(securePorts)); } public H2ServerTlsStrategy( final SSLContext sslContext, final SSLBufferMode sslBufferMode, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier) { this.sslContext = Args.notNull(sslContext, "SSL context"); this.sslBufferMode = sslBufferMode; this.initializer = initializer; this.verifier = verifier; this.securePortStrategy = null; } public H2ServerTlsStrategy( final SSLContext sslContext, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier) { this(sslContext, (SSLBufferMode) null, initializer, verifier); } public H2ServerTlsStrategy(final SSLContext sslContext, final SSLSessionVerifier verifier) { this(sslContext, (SSLBufferMode) null, null, verifier); } public H2ServerTlsStrategy(final SSLContext sslContext) { this(sslContext, (SSLBufferMode) null, null, null); } public H2ServerTlsStrategy() { this(SSLContexts.createSystemDefault()); } private boolean isApplicable(final SocketAddress localAddress) { return securePortStrategy == null || securePortStrategy.isSecure(localAddress); } @Override public void upgrade( final TransportSecurityLayer tlsSession, final NamedEndpoint endpoint, final Object attachment, final Timeout handshakeTimeout, final FutureCallback callback) { tlsSession.startTls( sslContext, endpoint, sslBufferMode, H2TlsSupport.enforceRequirements(attachment, initializer), verifier, handshakeTimeout, callback); } /** * @deprecated use {@link #upgrade(TransportSecurityLayer, NamedEndpoint, Object, Timeout, FutureCallback)} */ @Deprecated @Override public boolean upgrade( final TransportSecurityLayer tlsSession, final HttpHost host, final SocketAddress localAddress, final SocketAddress remoteAddress, final Object attachment, final Timeout handshakeTimeout) { if (isApplicable(localAddress)) { upgrade(tlsSession, host, attachment, handshakeTimeout, null); return true; } return false; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/H2TlsSupport.java0100644 0000000 0000000 00000007307 00000250600 025127 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.ssl; import javax.net.ssl.SSLParameters; import org.apache.hc.core5.http.ssl.TLS; import org.apache.hc.core5.http.ssl.TlsCiphers; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.util.ReflectionUtils; /** * HTTP/2 TLS support methods * * @since 5.0 */ public final class H2TlsSupport { public static void setEnableRetransmissions(final SSLParameters sslParameters, final boolean value) { ReflectionUtils.callSetter(sslParameters, "EnableRetransmissions", Boolean.TYPE, value); } /** * @deprecated Use {@link SSLParameters#setApplicationProtocols(String[])}. */ @Deprecated public static void setApplicationProtocols(final SSLParameters sslParameters, final String[] values) { ReflectionUtils.callSetter(sslParameters, "ApplicationProtocols", String[].class, values); } public static String[] selectApplicationProtocols(final Object attachment) { final HttpVersionPolicy versionPolicy = attachment instanceof HttpVersionPolicy ? (HttpVersionPolicy) attachment : HttpVersionPolicy.NEGOTIATE; switch (versionPolicy) { case FORCE_HTTP_1: return new String[] { ApplicationProtocol.HTTP_1_1.id }; case FORCE_HTTP_2: return new String[] { ApplicationProtocol.HTTP_2.id }; default: return new String[] { ApplicationProtocol.HTTP_2.id, ApplicationProtocol.HTTP_1_1.id }; } } /** * @return the given SSLParameters. * @since 5.3 */ public static SSLParameters enforceRequirements( final Object attachment, final SSLParameters sslParameters) { sslParameters.setProtocols(TLS.excludeWeak(sslParameters.getProtocols())); sslParameters.setCipherSuites(TlsCiphers.excludeH2Blacklisted(sslParameters.getCipherSuites())); setEnableRetransmissions(sslParameters, false); sslParameters.setApplicationProtocols(selectApplicationProtocols(attachment)); return sslParameters; } public static SSLSessionInitializer enforceRequirements( final Object attachment, final SSLSessionInitializer initializer) { return (endpoint, sslEngine) -> { sslEngine.setSSLParameters(enforceRequirements(attachment, sslEngine.getSSLParameters())); if (initializer != null) { initializer.initialize(endpoint, sslEngine); } }; } } httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/package-info.java0100644 0000000 0000000 00000002371 00000250600 025116 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * HTTP/2 specific TLS protocol support. */ package org.apache.hc.core5.http2.ssl; httpcore5-h2/src/test/0040755 0000000 0000000 00000000000 00000250600 013560 5ustar000000000 0000000 httpcore5-h2/src/test/java/0040755 0000000 0000000 00000000000 00000250600 014501 5ustar000000000 0000000 httpcore5-h2/src/test/java/org/0040755 0000000 0000000 00000000000 00000250600 015270 5ustar000000000 0000000 httpcore5-h2/src/test/java/org/apache/0040755 0000000 0000000 00000000000 00000250600 016511 5ustar000000000 0000000 httpcore5-h2/src/test/java/org/apache/hc/0040755 0000000 0000000 00000000000 00000250600 017103 5ustar000000000 0000000 httpcore5-h2/src/test/java/org/apache/hc/core5/0040755 0000000 0000000 00000000000 00000250600 020120 5ustar000000000 0000000 httpcore5-h2/src/test/java/org/apache/hc/core5/http2/0040755 0000000 0000000 00000000000 00000250600 021161 5ustar000000000 0000000 httpcore5-h2/src/test/java/org/apache/hc/core5/http2/ReadableByteChannelMock.java0100644 0000000 0000000 00000005324 00000250600 026453 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.ReadableByteChannel; public class ReadableByteChannelMock implements ReadableByteChannel { private final byte[][] chunks; private int chunkCount = 0; private ByteBuffer currentChunk; private boolean eof = false; private boolean closed = false; public ReadableByteChannelMock(final byte[]... chunks) { super(); this.chunks = chunks; } private void prepareChunk() { if (this.currentChunk == null || !this.currentChunk.hasRemaining()) { if (this.chunkCount < this.chunks.length) { final byte[] bytes = this.chunks[this.chunkCount]; this.chunkCount++; this.currentChunk = ByteBuffer.wrap(bytes); } else { this.eof = true; } } } @Override public int read(final ByteBuffer dst) throws IOException { if (this.closed) { throw new ClosedChannelException(); } prepareChunk(); if (this.eof) { return -1; } int i = 0; while (dst.hasRemaining() && this.currentChunk.hasRemaining()) { dst.put(this.currentChunk.get()); i++; } return i; } @Override public void close() throws IOException { this.closed = true; } @Override public boolean isOpen() { return !this.closed && !this.eof; } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/WritableByteChannelMock.java0100644 0000000 0000000 00000006730 00000250600 026527 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.WritableByteChannel; public class WritableByteChannelMock implements WritableByteChannel { private final int capacityLimit; private int capacityUsed; private ByteBuffer buf; private boolean closed; public WritableByteChannelMock(final int initialSize, final int capacityLimit) { this.buf = ByteBuffer.allocate(initialSize); this.capacityLimit = capacityLimit; } public WritableByteChannelMock(final int initialSize) { this(initialSize, 0); } private void expandCapacity(final int capacity) { final ByteBuffer oldbuffer = this.buf; this.buf = ByteBuffer.allocate(capacity); oldbuffer.flip(); this.buf.put(oldbuffer); } private void ensureCapacity(final int requiredCapacity) { if (requiredCapacity > this.buf.capacity()) { expandCapacity(requiredCapacity); } } @Override public int write(final ByteBuffer src) throws IOException { if (this.closed) { throw new ClosedChannelException(); } final int len = src.remaining(); ensureCapacity(this.buf.position() + len); if (this.capacityLimit > 0) { final int chunk = Math.min(this.capacityLimit - this.capacityUsed, len); if (chunk > 0) { final int limit = src.limit(); src.limit(src.position() + chunk); this.buf.put(src); src.limit(limit); this.capacityUsed += chunk; return chunk; } return 0; } this.buf.put(src); return len; } @Override public boolean isOpen() { return !this.closed; } @Override public void close() throws IOException { this.closed = true; } public void flush() { this.capacityUsed = 0; } public void reset() { this.capacityUsed = 0; this.buf.clear(); } public byte[] toByteArray() { final ByteBuffer dup = this.buf.duplicate(); dup.flip(); final byte[] bytes = new byte[dup.remaining()]; dup.get(bytes); return bytes; } }httpcore5-h2/src/test/java/org/apache/hc/core5/http2/config/0040755 0000000 0000000 00000000000 00000250600 022426 5ustar000000000 0000000 httpcore5-h2/src/test/java/org/apache/hc/core5/http2/config/H2ConfigTest.java0100644 0000000 0000000 00000006516 00000250600 025535 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.config; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; class H2ConfigTest { @Test void builder() { // Create and start requester final H2Config h2Config = H2Config.custom() .setPushEnabled(false) .build(); assertNotNull(h2Config); } @Test void checkValues() { // Create and start requester final H2Config h2Config = H2Config.custom() .setHeaderTableSize(1) .setMaxConcurrentStreams(1) .setMaxFrameSize(16384) .setPushEnabled(true) .setCompressionEnabled(true) .build(); assertEquals(1, h2Config.getHeaderTableSize()); assertEquals(1, h2Config.getMaxConcurrentStreams()); assertEquals(16384, h2Config.getMaxFrameSize()); assertTrue(h2Config.isPushEnabled()); assertTrue(h2Config.isCompressionEnabled()); } @Test void copy() { // Create and start requester final H2Config h2Config = H2Config.custom() .setHeaderTableSize(1) .setMaxConcurrentStreams(1) .setMaxFrameSize(16384) .setPushEnabled(true) .setCompressionEnabled(true) .build(); final H2Config.Builder builder = H2Config.copy(h2Config); final H2Config h2Config2 = builder.build(); assertAll( () -> assertEquals(h2Config.getHeaderTableSize(), h2Config2.getHeaderTableSize()), () -> assertEquals(h2Config.getInitialWindowSize(), h2Config2.getInitialWindowSize()), () -> assertEquals(h2Config.getMaxConcurrentStreams(), h2Config2.getMaxConcurrentStreams()), () -> assertEquals(h2Config.getMaxFrameSize(), h2Config2.getMaxFrameSize()), () -> assertEquals(h2Config.getMaxHeaderListSize(), h2Config2.getMaxHeaderListSize()) ); } }httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/0040755 0000000 0000000 00000000000 00000250600 022777 5ustar000000000 0000000 httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/ClassicH2RequestExecutionExample.java0100644 0000000 0000000 00000015721 00000250600 032171 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.examples; import java.io.BufferedReader; import java.io.InputStreamReader; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.concurrent.Future; import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http.nio.support.classic.ClassicToAsyncRequestProducer; import org.apache.hc.core5.http.nio.support.classic.ClassicToAsyncResponseConsumer; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.impl.nio.H2StreamListener; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.util.Timeout; /** * Example of HTTP/2 request execution with a classic I/O API compatibility bridge * that enables the use of standard {@link java.io.InputStream} / {@link java.io.OutputStream} * based data consumers / producers. *

> * Execution of individual message exchanges is performed at the current thread. */ @Experimental public class ClassicH2RequestExecutionExample { public static void main(final String[] args) throws Exception { // Create and start requester final H2Config h2Config = H2Config.custom() .setPushEnabled(false) .build(); final HttpAsyncRequester requester = H2RequesterBootstrap.bootstrap() .setH2Config(h2Config) .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2) .setStreamListener(new H2StreamListener() { @Override public void onHeaderInput(final HttpConnection connection, final int streamId, final List headers) { for (int i = 0; i < headers.size(); i++) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") << " + headers.get(i)); } } @Override public void onHeaderOutput(final HttpConnection connection, final int streamId, final List headers) { for (int i = 0; i < headers.size(); i++) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") >> " + headers.get(i)); } } @Override public void onFrameInput(final HttpConnection connection, final int streamId, final RawFrame frame) { } @Override public void onFrameOutput(final HttpConnection connection, final int streamId, final RawFrame frame) { } @Override public void onInputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { } @Override public void onOutputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { } }) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("HTTP requester shutting down"); requester.close(CloseMode.GRACEFUL); })); requester.start(); final HttpHost target = new HttpHost("nghttp2.org"); final Future future = requester.connect(target, Timeout.ofDays(5)); final AsyncClientEndpoint clientEndpoint = future.get(); final String[] requestUris = new String[] {"/httpbin/ip", "/httpbin/user-agent", "/httpbin/headers"}; for (final String requestUri: requestUris) { final ClassicHttpRequest request = ClassicRequestBuilder.get() .setHttpHost(target) .setPath(requestUri) .build(); final ClassicToAsyncRequestProducer requestProducer = new ClassicToAsyncRequestProducer(request, Timeout.ofMinutes(5)); final ClassicToAsyncResponseConsumer responseConsumer = new ClassicToAsyncResponseConsumer(Timeout.ofMinutes(5)); clientEndpoint.execute(requestProducer, responseConsumer, null); requestProducer.blockWaiting().execute(); try (ClassicHttpResponse response = responseConsumer.blockWaiting()) { System.out.println(requestUri + " -> " + response.getCode()); final HttpEntity entity = response.getEntity(); if (entity != null) { final ContentType contentType = ContentType.parse(entity.getContentType()); final Charset charset = ContentType.getCharset(contentType, StandardCharsets.UTF_8); try (final BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent(), charset))) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); } } } } } System.out.println("Shutting down I/O reactor"); requester.initiateShutdown(); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/ClassicH2ServerExample.java0100644 0000000 0000000 00000021105 00000250600 030114 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.examples; import java.io.BufferedWriter; import java.io.OutputStreamWriter; import java.net.InetSocketAddress; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.concurrent.DefaultThreadFactory; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.entity.EntityTemplate; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.nio.support.classic.ClassicToAsyncServerExchangeHandler; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.impl.nio.H2StreamListener; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2ServerBootstrap; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.net.URIBuilder; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.util.TimeValue; /** * Example of asynchronous embedded HTTP/2 server with a classic I/O API compatibility * bridge that enables the use of standard {@link java.io.InputStream} / {@link java.io.OutputStream} * based data consumers / producers. *

> * Execution of individual message exchanges is delegated to an {@link java.util.concurrent.Executor} * backed by a pool of threads. */ @Experimental public class ClassicH2ServerExample { public static void main(final String[] args) throws Exception { int port = 8080; if (args.length >= 1) { port = Integer.parseInt(args[0]); } final IOReactorConfig config = IOReactorConfig.custom() .setSoTimeout(15, TimeUnit.SECONDS) .setTcpNoDelay(true) .build(); final ExecutorService executorService = Executors.newFixedThreadPool( 25, new DefaultThreadFactory("worker-pool", true)); final HttpRequestHandler requestHandler = (request, response, context) -> { try { final HttpEntity requestEntity = request.getEntity(); if (requestEntity != null) { EntityUtils.consume(requestEntity); } final Map queryParams = new URIBuilder(request.getUri()).getQueryParams().stream() .collect(Collectors.toMap( NameValuePair::getName, NameValuePair::getValue, (s, s2) -> s)); final int n = Integer.parseInt(queryParams.getOrDefault("n", "10")); final String p = queryParams.getOrDefault("pattern", "huh?"); final HttpEntity responseEntity = new EntityTemplate( ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8), outputStream -> { try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8))) { for (int i = 0; i < n; i++) { writer.write(p); writer.write("\n"); } } }); response.setEntity(responseEntity); } catch (final URISyntaxException ex) { throw new ProtocolException("Invalid request URI", ex); } catch (final NumberFormatException ex) { throw new ProtocolException("Invalid query parameter", ex); } }; final RequestRouter requestRouter = RequestRouter.builder() .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", requestHandler) .build(); final HttpAsyncServer server = H2ServerBootstrap.bootstrap() .setIOReactorConfig(config) .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2) .setStreamListener(new H2StreamListener() { @Override public void onHeaderInput(final HttpConnection connection, final int streamId, final List headers) { for (int i = 0; i < headers.size(); i++) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") << " + headers.get(i)); } } @Override public void onHeaderOutput(final HttpConnection connection, final int streamId, final List headers) { for (int i = 0; i < headers.size(); i++) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") >> " + headers.get(i)); } } @Override public void onFrameInput(final HttpConnection connection, final int streamId, final RawFrame frame) { } @Override public void onFrameOutput(final HttpConnection connection, final int streamId, final RawFrame frame) { } @Override public void onInputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { } @Override public void onOutputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { } }) .setRequestRouter((request, context) -> { final HttpRequestHandler handler = requestRouter.resolve(request, context); return () -> new ClassicToAsyncServerExchangeHandler( executorService, handler, e -> e.printStackTrace(System.out)); }) .setExceptionCallback(e -> e.printStackTrace(System.out)) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("HTTP server shutting down"); server.close(CloseMode.GRACEFUL); executorService.shutdownNow(); })); server.start(); final Future future = server.listen(new InetSocketAddress(port), URIScheme.HTTP); final ListenerEndpoint listenerEndpoint = future.get(); System.out.print("Listening on " + listenerEndpoint.getAddress()); server.awaitShutdown(TimeValue.ofDays(Long.MAX_VALUE)); } } ././@LongLink0100644 0000000 0000000 00000000146 00000250600 011612 Lustar 0000000 0000000 httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2ConscriptRequestExecutionExample.javahttpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2ConscriptRequestExecutionExample.jav0100644 0000000 0000000 00000016770 00000250600 032420 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.examples; import java.security.Provider; import java.security.Security; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import javax.net.ssl.SSLContext; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.support.AsyncRequestBuilder; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.impl.nio.H2StreamListener; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap; import org.apache.hc.core5.http2.ssl.ConscryptClientTlsStrategy; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.ssl.SSLContexts; import org.apache.hc.core5.util.Timeout; import org.conscrypt.Conscrypt; /** * This example demonstrates how to execute HTTP/2 requests over TLS connections * with Conscrypt TLS library. */ public class H2ConscriptRequestExecutionExample { public static void main(final String[] args) throws Exception { // Create and start requester final H2Config h2Config = H2Config.custom() .setPushEnabled(false) .build(); final Provider conscryptProvider = Conscrypt.newProviderBuilder() .provideTrustManager(true) .build(); Security.insertProviderAt(conscryptProvider, 1); final SSLContext sslContext = SSLContexts.custom() .build(); final HttpAsyncRequester requester = H2RequesterBootstrap.bootstrap() .setH2Config(h2Config) // For JRE versions 11 or newer the standard H2ClientTlsStrategy should be used instead .setTlsStrategy(new ConscryptClientTlsStrategy(sslContext)) .setVersionPolicy(HttpVersionPolicy.NEGOTIATE) .setStreamListener(new H2StreamListener() { @Override public void onHeaderInput(final HttpConnection connection, final int streamId, final List headers) { for (int i = 0; i < headers.size(); i++) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") << " + headers.get(i)); } } @Override public void onHeaderOutput(final HttpConnection connection, final int streamId, final List headers) { for (int i = 0; i < headers.size(); i++) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") >> " + headers.get(i)); } } @Override public void onFrameInput(final HttpConnection connection, final int streamId, final RawFrame frame) { } @Override public void onFrameOutput(final HttpConnection connection, final int streamId, final RawFrame frame) { } @Override public void onInputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { } @Override public void onOutputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { } }) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("HTTP requester shutting down"); requester.close(CloseMode.GRACEFUL); })); requester.start(); final HttpHost target = new HttpHost("https", "nghttp2.org", 443); final Future future = requester.connect(target, Timeout.ofDays(5)); final AsyncClientEndpoint clientEndpoint = future.get(); final String[] requestUris = new String[] {"/httpbin/ip", "/httpbin/user-agent", "/httpbin/headers"}; final CountDownLatch latch = new CountDownLatch(requestUris.length); for (final String requestUri: requestUris) { clientEndpoint.execute( AsyncRequestBuilder.get() .setHttpHost(target) .setPath(requestUri) .build(), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), new FutureCallback>() { @Override public void completed(final Message message) { clientEndpoint.releaseAndReuse(); final HttpResponse response = message.getHead(); final String body = message.getBody(); System.out.println(requestUri + "->" + response.getCode() + " " + response.getVersion()); System.out.println(body); latch.countDown(); } @Override public void failed(final Exception ex) { clientEndpoint.releaseAndDiscard(); System.out.println(requestUri + "->" + ex); latch.countDown(); } @Override public void cancelled() { clientEndpoint.releaseAndDiscard(); System.out.println(requestUri + " cancelled"); latch.countDown(); } }); } latch.await(); System.out.println("Shutting down I/O reactor"); requester.initiateShutdown(); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2FileServerExample.java0100644 0000000 0000000 00000023436 00000250600 027423 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.examples; import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; import java.util.List; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EndpointDetails; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.nio.AsyncRequestConsumer; import org.apache.hc.core5.http.nio.AsyncServerRequestHandler; import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers; import org.apache.hc.core5.http.nio.entity.DiscardingEntityConsumer; import org.apache.hc.core5.http.nio.support.AsyncResponseBuilder; import org.apache.hc.core5.http.nio.support.BasicRequestConsumer; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.impl.nio.H2StreamListener; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2ServerBootstrap; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.util.TimeValue; /** * Example of asynchronous embedded HTTP/2 file server. */ public class H2FileServerExample { public static void main(final String[] args) throws Exception { if (args.length < 1) { System.err.println("Please specify document root directory"); System.exit(1); } // Document root directory final File docRoot = new File(args[0]); int port = 8080; if (args.length >= 2) { port = Integer.parseInt(args[1]); } final IOReactorConfig config = IOReactorConfig.custom() .setSoTimeout(15, TimeUnit.SECONDS) .setTcpNoDelay(true) .build(); final HttpAsyncServer server = H2ServerBootstrap.bootstrap() .setIOReactorConfig(config) .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2) .setStreamListener(new H2StreamListener() { @Override public void onHeaderInput(final HttpConnection connection, final int streamId, final List headers) { for (int i = 0; i < headers.size(); i++) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") << " + headers.get(i)); } } @Override public void onHeaderOutput(final HttpConnection connection, final int streamId, final List headers) { for (int i = 0; i < headers.size(); i++) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") >> " + headers.get(i)); } } @Override public void onFrameInput(final HttpConnection connection, final int streamId, final RawFrame frame) { } @Override public void onFrameOutput(final HttpConnection connection, final int streamId, final RawFrame frame) { } @Override public void onInputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { } @Override public void onOutputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { } }) .register("*", new AsyncServerRequestHandler>() { @Override public AsyncRequestConsumer> prepare( final HttpRequest request, final EntityDetails entityDetails, final HttpContext context) throws HttpException { return new BasicRequestConsumer<>(entityDetails != null ? new DiscardingEntityConsumer<>() : null); } @Override public void handle( final Message message, final ResponseTrigger responseTrigger, final HttpContext localContext) throws HttpException, IOException { final HttpCoreContext context = HttpCoreContext.cast(localContext); final HttpRequest request = message.getHead(); final URI requestUri; try { requestUri = request.getUri(); } catch (final URISyntaxException ex) { throw new ProtocolException(ex.getMessage(), ex); } final String path = requestUri.getPath(); final File file = new File(docRoot, path); if (!file.exists()) { System.out.println("File " + file.getPath() + " not found"); responseTrigger.submitResponse( AsyncResponseBuilder.create(HttpStatus.SC_NOT_FOUND) .setEntity("

File" + file.getPath() + " not found

", ContentType.TEXT_HTML) .build(), context); } else if (!file.canRead() || file.isDirectory()) { System.out.println("Cannot read file " + file.getPath()); responseTrigger.submitResponse( AsyncResponseBuilder.create(HttpStatus.SC_FORBIDDEN) .setEntity("

Access denied

", ContentType.TEXT_HTML) .build(), context); } else { final ContentType contentType; final String filename = TextUtils.toLowerCase(file.getName()); if (filename.endsWith(".txt")) { contentType = ContentType.TEXT_PLAIN; } else if (filename.endsWith(".html") || filename.endsWith(".htm")) { contentType = ContentType.TEXT_HTML; } else if (filename.endsWith(".xml")) { contentType = ContentType.TEXT_XML; } else { contentType = ContentType.DEFAULT_BINARY; } final EndpointDetails endpoint = context.getEndpointDetails(); System.out.println(endpoint + ": serving file " + file.getPath()); responseTrigger.submitResponse( AsyncResponseBuilder.create(HttpStatus.SC_OK) .setEntity(AsyncEntityProducers.create(file, contentType)) .build(), context); } } }) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("HTTP server shutting down"); server.close(CloseMode.GRACEFUL); })); server.start(); final Future future = server.listen(new InetSocketAddress(port), URIScheme.HTTP); final ListenerEndpoint listenerEndpoint = future.get(); System.out.print("Listening on " + listenerEndpoint.getAddress()); server.awaitShutdown(TimeValue.ofDays(Long.MAX_VALUE)); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2FullDuplexClientExample.java0100644 0000000 0000000 00000020232 00000250600 030567 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.examples; import java.io.IOException; import java.net.URI; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; import org.apache.hc.core5.http.nio.AsyncRequestProducer; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.RequestChannel; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.support.AsyncRequestBuilder; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.impl.nio.H2StreamListener; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.util.Timeout; /** * Example of full-duplex, streaming HTTP message exchanges with an asynchronous HTTP/2 requester. */ public class H2FullDuplexClientExample { public static void main(final String[] args) throws Exception { final IOReactorConfig ioReactorConfig = IOReactorConfig.custom() .setSoTimeout(5, TimeUnit.SECONDS) .build(); // Create and start requester final H2Config h2Config = H2Config.custom() .setPushEnabled(false) .setMaxConcurrentStreams(100) .build(); final HttpAsyncRequester requester = H2RequesterBootstrap.bootstrap() .setIOReactorConfig(ioReactorConfig) .setH2Config(h2Config) .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2) .setStreamListener(new H2StreamListener() { @Override public void onHeaderInput(final HttpConnection connection, final int streamId, final List headers) { for (int i = 0; i < headers.size(); i++) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") << " + headers.get(i)); } } @Override public void onHeaderOutput(final HttpConnection connection, final int streamId, final List headers) { for (int i = 0; i < headers.size(); i++) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") >> " + headers.get(i)); } } @Override public void onFrameInput(final HttpConnection connection, final int streamId, final RawFrame frame) { } @Override public void onFrameOutput(final HttpConnection connection, final int streamId, final RawFrame frame) { } @Override public void onInputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { } @Override public void onOutputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { } }) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("HTTP requester shutting down"); requester.close(CloseMode.GRACEFUL); })); requester.start(); final URI requestUri = new URI("http://nghttp2.org/httpbin/post"); final AsyncRequestProducer requestProducer = AsyncRequestBuilder.post(requestUri) .setEntity("stuff") .build(); final BasicResponseConsumer responseConsumer = new BasicResponseConsumer<>( new StringAsyncEntityConsumer()); final CountDownLatch latch = new CountDownLatch(1); requester.execute(new AsyncClientExchangeHandler() { @Override public void releaseResources() { requestProducer.releaseResources(); responseConsumer.releaseResources(); latch.countDown(); } @Override public void cancel() { System.out.println(requestUri + " cancelled"); } @Override public void failed(final Exception cause) { System.out.println(requestUri + "->" + cause); } @Override public void produceRequest(final RequestChannel channel, final HttpContext httpContext) throws HttpException, IOException { requestProducer.sendRequest(channel, httpContext); } @Override public int available() { return requestProducer.available(); } @Override public void produce(final DataStreamChannel channel) throws IOException { requestProducer.produce(channel); } @Override public void consumeInformation(final HttpResponse response, final HttpContext httpContext) throws HttpException, IOException { System.out.println(requestUri + "->" + response.getCode()); } @Override public void consumeResponse(final HttpResponse response, final EntityDetails entityDetails, final HttpContext httpContext) throws HttpException, IOException { System.out.println(requestUri + "->" + response.getCode()); responseConsumer.consumeResponse(response, entityDetails, httpContext, null); } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { responseConsumer.updateCapacity(capacityChannel); } @Override public void consume(final ByteBuffer src) throws IOException { responseConsumer.consume(src); } @Override public void streamEnd(final List trailers) throws HttpException, IOException { responseConsumer.streamEnd(trailers); } }, Timeout.ofSeconds(30), HttpCoreContext.create()); latch.await(); System.out.println("Shutting down I/O reactor"); requester.initiateShutdown(); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2FullDuplexServerExample.java0100644 0000000 0000000 00000024021 00000250600 030617 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.examples; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.impl.nio.H2StreamListener; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2ServerBootstrap; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.util.TimeValue; /** * Example of full-duplex, streaming HTTP message exchanges with an asynchronous embedded HTTP/2 server. */ public class H2FullDuplexServerExample { public static void main(final String[] args) throws Exception { int port = 8080; if (args.length >= 1) { port = Integer.parseInt(args[0]); } final IOReactorConfig config = IOReactorConfig.custom() .setSoTimeout(15, TimeUnit.SECONDS) .setTcpNoDelay(true) .build(); final H2Config h2Config = H2Config.custom() .setPushEnabled(true) .setMaxConcurrentStreams(100) .build(); final HttpAsyncServer server = H2ServerBootstrap.bootstrap() .setIOReactorConfig(config) .setH2Config(h2Config) .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2) .setStreamListener(new H2StreamListener() { @Override public void onHeaderInput(final HttpConnection connection, final int streamId, final List headers) { for (int i = 0; i < headers.size(); i++) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") << " + headers.get(i)); } } @Override public void onHeaderOutput(final HttpConnection connection, final int streamId, final List headers) { for (int i = 0; i < headers.size(); i++) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") >> " + headers.get(i)); } } @Override public void onFrameInput(final HttpConnection connection, final int streamId, final RawFrame frame) { } @Override public void onFrameOutput(final HttpConnection connection, final int streamId, final RawFrame frame) { } @Override public void onInputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { } @Override public void onOutputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { } }) .register("/echo", () -> new AsyncServerExchangeHandler() { ByteBuffer buffer = ByteBuffer.allocate(2048); CapacityChannel inputCapacityChannel; DataStreamChannel outputDataChannel; boolean endStream; private void ensureCapacity(final int chunk) { if (buffer.remaining() < chunk) { final ByteBuffer oldBuffer = buffer; oldBuffer.flip(); buffer = ByteBuffer.allocate(oldBuffer.remaining() + (chunk > 2048 ? chunk : 2048)); buffer.put(oldBuffer); } } @Override public void handleRequest( final HttpRequest request, final EntityDetails entityDetails, final ResponseChannel responseChannel, final HttpContext context) throws HttpException, IOException { final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK); responseChannel.sendResponse(response, entityDetails, context); } @Override public void consume(final ByteBuffer src) throws IOException { if (buffer.position() == 0) { if (outputDataChannel != null) { outputDataChannel.write(src); } } if (src.hasRemaining()) { ensureCapacity(src.remaining()); buffer.put(src); if (outputDataChannel != null) { outputDataChannel.requestOutput(); } } } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { if (buffer.hasRemaining()) { capacityChannel.update(buffer.remaining()); inputCapacityChannel = null; } else { inputCapacityChannel = capacityChannel; } } @Override public void streamEnd(final List trailers) throws IOException { endStream = true; if (buffer.position() == 0) { if (outputDataChannel != null) { outputDataChannel.endStream(); } } else { if (outputDataChannel != null) { outputDataChannel.requestOutput(); } } } @Override public int available() { return buffer.position(); } @Override public void produce(final DataStreamChannel channel) throws IOException { outputDataChannel = channel; buffer.flip(); if (buffer.hasRemaining()) { channel.write(buffer); } buffer.compact(); if (buffer.position() == 0 && endStream) { channel.endStream(); } final CapacityChannel capacityChannel = inputCapacityChannel; if (capacityChannel != null && buffer.hasRemaining()) { capacityChannel.update(buffer.remaining()); } } @Override public void failed(final Exception cause) { if (!(cause instanceof SocketException)) { cause.printStackTrace(System.out); } } @Override public void releaseResources() { } }) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("HTTP server shutting down"); server.close(CloseMode.GRACEFUL); })); server.start(); final Future future = server.listen(new InetSocketAddress(port), URIScheme.HTTP); final ListenerEndpoint listenerEndpoint = future.get(); System.out.print("Listening on " + listenerEndpoint.getAddress()); server.awaitShutdown(TimeValue.ofDays(Long.MAX_VALUE)); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2GreetingServer.java0100644 0000000 0000000 00000017023 00000250600 026767 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.examples; import java.io.IOException; import java.net.InetSocketAddress; import java.time.Instant; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EndpointDetails; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.nio.AsyncEntityConsumer; import org.apache.hc.core5.http.nio.AsyncRequestConsumer; import org.apache.hc.core5.http.nio.AsyncServerRequestHandler; import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers; import org.apache.hc.core5.http.nio.entity.DiscardingEntityConsumer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.support.AbstractServerExchangeHandler; import org.apache.hc.core5.http.nio.support.BasicRequestConsumer; import org.apache.hc.core5.http.nio.support.BasicResponseProducer; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2ServerBootstrap; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.net.WWWFormCodec; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.util.TimeValue; /** * Example HTTP2 server that reads an entity body and responds back with a greeting. * *
 * {@code
 * $ curl  -id name=bob localhost:8080
 * HTTP/1.1 200 OK
 * Date: Sat, 25 May 2019 03:44:49 GMT
 * Server: Apache-HttpCore/5.0-beta8-SNAPSHOT (Java/1.8.0_202)
 * Transfer-Encoding: chunked
 * Content-Type: text/plain; charset=UTF-8
 *
 * Hello bob
 * }
*

* This examples uses a {@link AbstractServerExchangeHandler} for the basic request / response processing cycle. */ public class H2GreetingServer { public static void main(final String[] args) throws ExecutionException, InterruptedException { int port = 8080; if (args.length >= 1) { port = Integer.parseInt(args[0]); } final HttpAsyncServer server = H2ServerBootstrap.bootstrap() .setH2Config(H2Config.DEFAULT) .setIOReactorConfig(IOReactorConfig.DEFAULT) .setVersionPolicy(HttpVersionPolicy.NEGOTIATE) // fallback to HTTP/1 as needed // wildcard path matcher: .register("*", CustomServerExchangeHandler::new) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("HTTP server shutting down"); server.close(CloseMode.GRACEFUL); })); server.start(); final Future future = server.listen(new InetSocketAddress(port), URIScheme.HTTP); final ListenerEndpoint listenerEndpoint = future.get(); System.out.println("Listening on " + listenerEndpoint.getAddress()); server.awaitShutdown(TimeValue.ofDays(Long.MAX_VALUE)); } static class CustomServerExchangeHandler extends AbstractServerExchangeHandler> { @Override protected AsyncRequestConsumer> supplyConsumer( final HttpRequest request, final EntityDetails entityDetails, final HttpContext context) { // if there's no body don't try to parse entity: AsyncEntityConsumer entityConsumer = new DiscardingEntityConsumer<>(); if (entityDetails != null) { entityConsumer = new StringAsyncEntityConsumer(); } //noinspection unchecked return new BasicRequestConsumer<>(entityConsumer); } @Override protected void handle(final Message requestMessage, final AsyncServerRequestHandler.ResponseTrigger responseTrigger, final HttpContext localContext) throws HttpException, IOException { final HttpCoreContext context = HttpCoreContext.cast(localContext); final EndpointDetails endpoint = context.getEndpointDetails(); final HttpRequest req = requestMessage.getHead(); final String httpEntity = requestMessage.getBody(); // generic success response: final HttpResponse resp = new BasicHttpResponse(200); // recording the request System.out.printf("[%s] %s %s %s%n", Instant.now(), endpoint != null ? endpoint.getRemoteAddress() : null, req.getMethod(), req.getPath()); // Request without an entity - GET/HEAD/DELETE if (httpEntity == null) { responseTrigger.submitResponse( new BasicResponseProducer(resp), context); return; } // Request with an entity - POST/PUT final Header cth = req.getHeader(HttpHeaders.CONTENT_TYPE); final ContentType contentType = cth != null ? ContentType.parse(cth.getValue()) : null; String name = "stranger"; if (contentType != null && contentType.isSameMimeType(ContentType.APPLICATION_FORM_URLENCODED)) { // decoding the form entity into key/value pairs: final List args = WWWFormCodec.parse(httpEntity, contentType.getCharset()); if (!args.isEmpty()) { name = args.get(0).getValue(); } } // composing greeting: final String greeting = String.format("Hello %s\n", name); responseTrigger.submitResponse( new BasicResponseProducer(resp, AsyncEntityProducers.create(greeting)), context); } } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2MultiStreamExecutionExample.java0100644 0000000 0000000 00000015744 00000250600 031512 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.examples; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.support.AsyncRequestBuilder; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.impl.nio.H2StreamListener; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.util.Timeout; /** * Example of HTTP/2 concurrent request execution using multiple streams. */ public class H2MultiStreamExecutionExample { public static void main(final String[] args) throws Exception { // Create and start requester final IOReactorConfig ioReactorConfig = IOReactorConfig.custom() .setSoTimeout(5, TimeUnit.SECONDS) .build(); final H2Config h2Config = H2Config.custom() .setPushEnabled(false) .setMaxConcurrentStreams(100) .build(); final HttpAsyncRequester requester = H2RequesterBootstrap.bootstrap() .setIOReactorConfig(ioReactorConfig) .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2) .setH2Config(h2Config) .setStreamListener(new H2StreamListener() { @Override public void onHeaderInput(final HttpConnection connection, final int streamId, final List headers) { for (int i = 0; i < headers.size(); i++) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") << " + headers.get(i)); } } @Override public void onHeaderOutput(final HttpConnection connection, final int streamId, final List headers) { for (int i = 0; i < headers.size(); i++) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") >> " + headers.get(i)); } } @Override public void onFrameInput(final HttpConnection connection, final int streamId, final RawFrame frame) { } @Override public void onFrameOutput(final HttpConnection connection, final int streamId, final RawFrame frame) { } @Override public void onInputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { } @Override public void onOutputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { } }) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("HTTP requester shutting down"); requester.close(CloseMode.GRACEFUL); })); requester.start(); final HttpHost target = new HttpHost("nghttp2.org"); final String[] requestUris = new String[] {"/httpbin/ip", "/httpbin/user-agent", "/httpbin/headers"}; final Future future = requester.connect(target, Timeout.ofSeconds(5)); final AsyncClientEndpoint clientEndpoint = future.get(); final CountDownLatch latch = new CountDownLatch(requestUris.length); for (final String requestUri: requestUris) { clientEndpoint.execute( AsyncRequestBuilder.get() .setHttpHost(target) .setPath(requestUri) .build(), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), new FutureCallback>() { @Override public void completed(final Message message) { latch.countDown(); final HttpResponse response = message.getHead(); final String body = message.getBody(); System.out.println(requestUri + "->" + response.getCode()); System.out.println(body); } @Override public void failed(final Exception ex) { latch.countDown(); System.out.println(requestUri + "->" + ex); } @Override public void cancelled() { latch.countDown(); System.out.println(requestUri + " cancelled"); } }); } latch.await(); // Manually release client endpoint when done !!! clientEndpoint.releaseAndDiscard(); System.out.println("Shutting down I/O reactor"); requester.initiateShutdown(); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2RequestExecutionExample.java0100644 0000000 0000000 00000015300 00000250600 030660 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.examples; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.support.AsyncRequestBuilder; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.impl.nio.H2StreamListener; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.util.Timeout; /** * Example of HTTP/2 request execution. */ public class H2RequestExecutionExample { public static void main(final String[] args) throws Exception { // Create and start requester final H2Config h2Config = H2Config.custom() .setPushEnabled(false) .build(); final HttpAsyncRequester requester = H2RequesterBootstrap.bootstrap() .setH2Config(h2Config) .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2) .setStreamListener(new H2StreamListener() { @Override public void onHeaderInput(final HttpConnection connection, final int streamId, final List headers) { for (int i = 0; i < headers.size(); i++) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") << " + headers.get(i)); } } @Override public void onHeaderOutput(final HttpConnection connection, final int streamId, final List headers) { for (int i = 0; i < headers.size(); i++) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") >> " + headers.get(i)); } } @Override public void onFrameInput(final HttpConnection connection, final int streamId, final RawFrame frame) { } @Override public void onFrameOutput(final HttpConnection connection, final int streamId, final RawFrame frame) { } @Override public void onInputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { } @Override public void onOutputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { } }) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("HTTP requester shutting down"); requester.close(CloseMode.GRACEFUL); })); requester.start(); final HttpHost target = new HttpHost("nghttp2.org"); final Future future = requester.connect(target, Timeout.ofSeconds(5)); final AsyncClientEndpoint clientEndpoint = future.get(); final String[] requestUris = new String[] {"/httpbin/ip", "/httpbin/user-agent", "/httpbin/headers"}; final CountDownLatch latch = new CountDownLatch(requestUris.length); for (final String requestUri: requestUris) { clientEndpoint.execute( AsyncRequestBuilder.get() .setHttpHost(target) .setPath(requestUri) .build(), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), new FutureCallback>() { @Override public void completed(final Message message) { clientEndpoint.releaseAndReuse(); final HttpResponse response = message.getHead(); final String body = message.getBody(); System.out.println(requestUri + "->" + response.getCode()); System.out.println(body); latch.countDown(); } @Override public void failed(final Exception ex) { clientEndpoint.releaseAndDiscard(); System.out.println(requestUri + "->" + ex); latch.countDown(); } @Override public void cancelled() { clientEndpoint.releaseAndDiscard(); System.out.println(requestUri + " cancelled"); latch.countDown(); } }); } latch.await(); System.out.println("Shutting down I/O reactor"); requester.initiateShutdown(); } } ././@LongLink0100644 0000000 0000000 00000000151 00000250600 011606 Lustar 0000000 0000000 httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2RequestExecutionWithPriorityExample.javahttpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2RequestExecutionWithPriorityExample.0100644 0000000 0000000 00000017124 00000250600 032422 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.examples; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http.support.BasicRequestBuilder; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.impl.H2Processors; import org.apache.hc.core5.http2.impl.nio.H2StreamListener; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap; import org.apache.hc.core5.http2.priority.PriorityFormatter; import org.apache.hc.core5.http2.priority.PriorityValue; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.util.Timeout; /** * HTTP/2 request that emits RFC 9218 Priority via "Priority" header. */ @Experimental public class H2RequestExecutionWithPriorityExample { public static void main(final String[] args) throws Exception { // Force HTTP/2 and disable push for a cleaner demo final H2Config h2Config = H2Config.custom() .setPushEnabled(false) .build(); final HttpAsyncRequester requester = H2RequesterBootstrap.bootstrap() .setH2Config(h2Config) .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2) .setHttpProcessor(H2Processors.client()) // includes H2RequestPriority .setStreamListener(new H2StreamListener() { @Override public void onHeaderInput(final HttpConnection connection, final int streamId, final List headers) { for (final Header h : headers) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") << " + h); } } @Override public void onHeaderOutput(final HttpConnection connection, final int streamId, final List headers) { for (final Header h : headers) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") >> " + h); } } @Override public void onFrameInput(final HttpConnection c, final int id, final RawFrame f) { } @Override public void onFrameOutput(final HttpConnection c, final int id, final RawFrame f) { } @Override public void onInputFlowControl(final HttpConnection c, final int id, final int d, final int s) { } @Override public void onOutputFlowControl(final HttpConnection c, final int id, final int d, final int s) { } }) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("HTTP requester shutting down"); requester.close(CloseMode.GRACEFUL); })); requester.start(); final HttpHost target = new HttpHost("nghttp2.org"); final Future future = requester.connect(target, Timeout.ofSeconds(30)); final AsyncClientEndpoint clientEndpoint = future.get(); final CountDownLatch latch = new CountDownLatch(2); // ---- Request 1: Explicit non-default priority -> header MUST be emitted executeWithPriority(clientEndpoint, target, "/httpbin/headers", PriorityValue.of(0, true), latch); // ---- Request 2: RFC defaults -> header MUST be omitted by the interceptor executeWithPriority(clientEndpoint, target, "/httpbin/user-agent", PriorityValue.defaults(), latch); System.out.println("Shutting down I/O reactor"); requester.initiateShutdown(); } private static void executeWithPriority( final AsyncClientEndpoint clientEndpoint, final HttpHost target, final String requestUri, final PriorityValue priorityValue, final CountDownLatch latch) throws Exception { final BasicHttpRequest request = BasicRequestBuilder.get() .setHttpHost(target) .setPath(requestUri) .build(); if (!PriorityValue.defaults().equals(priorityValue)) { request.addHeader(PriorityFormatter.formatHeader(priorityValue)); } clientEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), new FutureCallback>() { @Override public void completed(final Message message) { clientEndpoint.releaseAndReuse(); final HttpResponse response = message.getHead(); final String body = message.getBody(); System.out.println(requestUri + "->" + response.getCode()); System.out.println(body); latch.countDown(); } @Override public void failed(final Exception ex) { clientEndpoint.releaseAndDiscard(); System.out.println(requestUri + "->" + ex); latch.countDown(); } @Override public void cancelled() { clientEndpoint.releaseAndDiscard(); System.out.println(requestUri + " cancelled"); latch.countDown(); } }); } }httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2ViaHttp1ProxyExecutionExample.java0100644 0000000 0000000 00000025742 00000250600 031745 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.examples; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.concurrent.ComplexFuture; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.concurrent.FutureContribution; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http.nio.entity.DiscardingEntityConsumer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.ssl.TlsUpgradeCapable; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.impl.nio.H2StreamListener; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.util.Timeout; /** * Example of asynchronous HTTP/2 request execution via a HTTP/1.1 proxy. */ public class H2ViaHttp1ProxyExecutionExample { public static void main(final String[] args) throws Exception { // Create and start requester final H2Config h2Config = H2Config.custom() .setPushEnabled(false) .build(); final HttpAsyncRequester requester = H2RequesterBootstrap.bootstrap() .setH2Config(h2Config) .setVersionPolicy(HttpVersionPolicy.NEGOTIATE) .setStreamListener(new Http1StreamListener() { @Override public void onRequestHead(final HttpConnection connection, final HttpRequest request) { System.out.println(connection.getRemoteAddress() + " " + new RequestLine(request)); } @Override public void onResponseHead(final HttpConnection connection, final HttpResponse response) { System.out.println(connection.getRemoteAddress() + " " + new StatusLine(response)); } @Override public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) { if (keepAlive) { System.out.println(connection.getRemoteAddress() + " exchange completed (connection kept alive)"); } else { System.out.println(connection.getRemoteAddress() + " exchange completed (connection closed)"); } } }) .setStreamListener(new H2StreamListener() { @Override public void onHeaderInput(final HttpConnection connection, final int streamId, final List headers) { for (int i = 0; i < headers.size(); i++) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") << " + headers.get(i)); } } @Override public void onHeaderOutput(final HttpConnection connection, final int streamId, final List headers) { for (int i = 0; i < headers.size(); i++) { System.out.println(connection.getRemoteAddress() + " (" + streamId + ") >> " + headers.get(i)); } } @Override public void onFrameInput(final HttpConnection connection, final int streamId, final RawFrame frame) { } @Override public void onFrameOutput(final HttpConnection connection, final int streamId, final RawFrame frame) { } @Override public void onInputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { } @Override public void onOutputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { } }) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("HTTP requester shutting down"); requester.close(CloseMode.GRACEFUL); })); requester.start(); final HttpHost proxy = new HttpHost("localhost", 8888); final HttpHost target = new HttpHost("https", "nghttp2.org"); final ComplexFuture tunnelFuture = new ComplexFuture<>(null); tunnelFuture.setDependency(requester.connect( proxy, Timeout.ofSeconds(30), null, new FutureContribution(tunnelFuture) { @Override public void completed(final AsyncClientEndpoint endpoint) { if (endpoint instanceof TlsUpgradeCapable) { final HttpRequest connect = new BasicHttpRequest(Method.CONNECT, proxy, target.toHostString()); endpoint.execute( new BasicRequestProducer(connect, null), new BasicResponseConsumer<>(new DiscardingEntityConsumer<>()), new FutureContribution>(tunnelFuture) { @Override public void completed(final Message message) { final HttpResponse response = message.getHead(); if (response.getCode() == HttpStatus.SC_OK) { ((TlsUpgradeCapable) endpoint).tlsUpgrade( target, new FutureContribution(tunnelFuture) { @Override public void completed(final ProtocolIOSession protocolSession) { System.out.println("Tunnel to " + target + " via " + proxy + " established"); tunnelFuture.completed(endpoint); } }); } else { tunnelFuture.failed(new HttpException("Tunnel refused: " + new StatusLine(response))); } } }); } else { tunnelFuture.failed(new IllegalStateException("TLS upgrade not supported")); } } })); final String[] requestUris = new String[] {"/httpbin/ip", "/httpbin/user-agent", "/httpbin/headers"}; final AsyncClientEndpoint endpoint = tunnelFuture.get(1, TimeUnit.MINUTES); try { final CountDownLatch latch = new CountDownLatch(requestUris.length); for (final String requestUri : requestUris) { endpoint.execute( new BasicRequestProducer(Method.GET, target, requestUri), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), new FutureCallback>() { @Override public void completed(final Message message) { final HttpResponse response = message.getHead(); final String body = message.getBody(); System.out.println(requestUri + "->" + response.getCode()); System.out.println(body); latch.countDown(); } @Override public void failed(final Exception ex) { System.out.println(requestUri + "->" + ex); latch.countDown(); } @Override public void cancelled() { System.out.println(requestUri + " cancelled"); latch.countDown(); } }); } latch.await(); } finally { endpoint.releaseAndDiscard(); } System.out.println("Shutting down I/O reactor"); requester.initiateShutdown(); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/0040755 0000000 0000000 00000000000 00000250600 022253 5ustar000000000 0000000 httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestDefaultFrameFactory.java0100644 0000000 0000000 00000010270 00000250600 027642 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.frame; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http2.H2Error; import org.apache.hc.core5.http2.config.H2Param; import org.apache.hc.core5.http2.config.H2Setting; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestDefaultFrameFactory { @Test void testDataFrame() { final FrameFactory frameFactory = new DefaultFrameFactory(); final byte[] data = new byte[]{'a', 'b', 'c', 'd', 'e', 'f'}; final Frame dataFrame = frameFactory.createData(23, ByteBuffer.wrap(data), true); Assertions.assertEquals(FrameType.DATA.value, dataFrame.getType()); Assertions.assertEquals(23, dataFrame.getStreamId()); Assertions.assertEquals(1L, dataFrame.getFlags()); } @Test void testSettingFrame() { final FrameFactory frameFactory = new DefaultFrameFactory(); final Frame settingsFrame = frameFactory.createSettings( new H2Setting(H2Param.HEADER_TABLE_SIZE, 1024), new H2Setting(H2Param.MAX_CONCURRENT_STREAMS, 1)); Assertions.assertEquals(FrameType.SETTINGS.value, settingsFrame.getType()); Assertions.assertEquals(0, settingsFrame.getStreamId()); Assertions.assertEquals(0, settingsFrame.getFlags()); final ByteBuffer payload = settingsFrame.getPayload(); Assertions.assertNotNull(payload); Assertions.assertEquals(12, payload.remaining()); } @Test void testResetStreamFrame() { final FrameFactory frameFactory = new DefaultFrameFactory(); final Frame rstStreamFrame = frameFactory.createResetStream(12, H2Error.INTERNAL_ERROR); Assertions.assertEquals(FrameType.RST_STREAM.value, rstStreamFrame.getType()); Assertions.assertEquals(12, rstStreamFrame.getStreamId()); Assertions.assertEquals(0, rstStreamFrame.getFlags()); final ByteBuffer payload = rstStreamFrame.getPayload(); Assertions.assertNotNull(payload); Assertions.assertEquals(4, payload.remaining()); Assertions.assertEquals(H2Error.INTERNAL_ERROR.getCode(), payload.getInt()); } @Test void testGoAwayFrame() { final FrameFactory frameFactory = new DefaultFrameFactory(); final Frame goAwayFrame = frameFactory.createGoAway(13, H2Error.INTERNAL_ERROR, "Oopsie"); Assertions.assertEquals(FrameType.GOAWAY.value, goAwayFrame.getType()); Assertions.assertEquals(0, goAwayFrame.getStreamId()); Assertions.assertEquals(0, goAwayFrame.getFlags()); final ByteBuffer payload = goAwayFrame.getPayload(); Assertions.assertNotNull(payload); Assertions.assertEquals(13, payload.getInt()); Assertions.assertEquals(H2Error.INTERNAL_ERROR.getCode(), payload.getInt()); final byte[] tmp = new byte[payload.remaining()]; payload.get(tmp); Assertions.assertEquals("Oopsie", new String(tmp, StandardCharsets.US_ASCII)); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestFrameFlag.java0100644 0000000 0000000 00000003003 00000250600 025573 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.frame; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestFrameFlag { @Test void testFrameFlagBasics() { final int flags = FrameFlag.of(FrameFlag.END_STREAM, FrameFlag.PADDED, FrameFlag.PRIORITY); Assertions.assertEquals(0x01 | 0x08 | 0x20, flags); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestH2Settings.java0100644 0000000 0000000 00000004366 00000250600 025756 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.frame; import org.apache.hc.core5.http2.config.H2Param; import org.apache.hc.core5.http2.config.H2Setting; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestH2Settings { @Test void testH2ParamBasics() { for (final H2Param param: H2Param.values()) { Assertions.assertEquals(param, H2Param.valueOf(param.getCode())); Assertions.assertEquals(param.name(), H2Param.toString(param.getCode())); } Assertions.assertNull(H2Param.valueOf(0)); Assertions.assertNull(H2Param.valueOf(10)); Assertions.assertEquals("0", H2Param.toString(0)); Assertions.assertEquals("10", H2Param.toString(10)); } @Test void testH2SettingBasics() { final H2Setting setting1 = new H2Setting(H2Param.ENABLE_PUSH, 0); final H2Setting setting2 = new H2Setting(H2Param.INITIAL_WINDOW_SIZE, 1024); Assertions.assertEquals("ENABLE_PUSH: 0", setting1.toString()); Assertions.assertEquals("INITIAL_WINDOW_SIZE: 1024", setting2.toString()); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/0040755 0000000 0000000 00000000000 00000250600 022247 5ustar000000000 0000000 httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestFifoBuffer.java0100644 0000000 0000000 00000013127 00000250600 025770 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestFifoBuffer { @Test void testAddRemoveCycle() { final FifoBuffer fifoBuffer = new FifoBuffer(5); final HPackHeader h1 = new HPackHeader("h", "1"); final HPackHeader h2 = new HPackHeader("h", "2"); final HPackHeader h3 = new HPackHeader("h", "3"); final HPackHeader h4 = new HPackHeader("h", "4"); for (int i = 0; i < 20; i++) { Assertions.assertEquals(0, fifoBuffer.size()); Assertions.assertSame(null, fifoBuffer.getFirst()); Assertions.assertSame(null, fifoBuffer.getLast()); fifoBuffer.addFirst(h1); Assertions.assertSame(h1, fifoBuffer.getFirst()); Assertions.assertSame(h1, fifoBuffer.getLast()); Assertions.assertEquals(1, fifoBuffer.size()); fifoBuffer.addFirst(h2); Assertions.assertEquals(2, fifoBuffer.size()); Assertions.assertSame(h2, fifoBuffer.getFirst()); Assertions.assertSame(h1, fifoBuffer.getLast()); fifoBuffer.addFirst(h3); Assertions.assertEquals(3, fifoBuffer.size()); Assertions.assertSame(h3, fifoBuffer.getFirst()); Assertions.assertSame(h1, fifoBuffer.getLast()); fifoBuffer.addFirst(h4); Assertions.assertEquals(4, fifoBuffer.size()); Assertions.assertSame(h4, fifoBuffer.getFirst()); Assertions.assertSame(h1, fifoBuffer.getLast()); Assertions.assertSame(h4, fifoBuffer.get(0)); Assertions.assertSame(h3, fifoBuffer.get(1)); Assertions.assertSame(h2, fifoBuffer.get(2)); Assertions.assertSame(h1, fifoBuffer.get(3)); fifoBuffer.removeLast(); Assertions.assertEquals(3, fifoBuffer.size()); Assertions.assertSame(h4, fifoBuffer.getFirst()); Assertions.assertSame(h2, fifoBuffer.getLast()); fifoBuffer.removeLast(); Assertions.assertEquals(2, fifoBuffer.size()); Assertions.assertSame(h4, fifoBuffer.getFirst()); Assertions.assertSame(h3, fifoBuffer.getLast()); fifoBuffer.removeLast(); Assertions.assertEquals(1, fifoBuffer.size()); Assertions.assertSame(h4, fifoBuffer.getFirst()); Assertions.assertSame(h4, fifoBuffer.getLast()); fifoBuffer.removeLast(); Assertions.assertEquals(0, fifoBuffer.size()); Assertions.assertSame(null, fifoBuffer.getFirst()); Assertions.assertSame(null, fifoBuffer.getLast()); } } @Test void testExpand() { final HPackHeader h1 = new HPackHeader("h", "1"); final HPackHeader h2 = new HPackHeader("h", "2"); final HPackHeader h3 = new HPackHeader("h", "3"); final HPackHeader h4 = new HPackHeader("h", "4"); for (int i = 0; i < 10; i++) { final FifoBuffer fifoBuffer = new FifoBuffer(1); for (int n = 0; n < i; n++) { fifoBuffer.addFirst(h1); fifoBuffer.removeLast(); } Assertions.assertEquals(0, fifoBuffer.size()); Assertions.assertSame(null, fifoBuffer.getFirst()); Assertions.assertSame(null, fifoBuffer.getLast()); fifoBuffer.addFirst(h1); Assertions.assertSame(h1, fifoBuffer.getFirst()); Assertions.assertSame(h1, fifoBuffer.getLast()); Assertions.assertEquals(1, fifoBuffer.size()); fifoBuffer.addFirst(h2); Assertions.assertEquals(2, fifoBuffer.size()); Assertions.assertSame(h2, fifoBuffer.getFirst()); Assertions.assertSame(h1, fifoBuffer.getLast()); fifoBuffer.addFirst(h3); Assertions.assertEquals(3, fifoBuffer.size()); Assertions.assertSame(h3, fifoBuffer.getFirst()); Assertions.assertSame(h1, fifoBuffer.getLast()); fifoBuffer.addFirst(h4); Assertions.assertEquals(4, fifoBuffer.size()); Assertions.assertSame(h4, fifoBuffer.getFirst()); Assertions.assertSame(h1, fifoBuffer.getLast()); Assertions.assertSame(h4, fifoBuffer.get(0)); Assertions.assertSame(h3, fifoBuffer.get(1)); Assertions.assertSame(h2, fifoBuffer.get(2)); Assertions.assertSame(h1, fifoBuffer.get(3)); } } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestFifoLinkedList.java0100644 0000000 0000000 00000015355 00000250600 026626 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestFifoLinkedList { @Test void testAddRemoveCycle() { final FifoLinkedList fifoLinkedList = new FifoLinkedList(); final HPackHeader h1 = new HPackHeader("h", "1"); final HPackHeader h2 = new HPackHeader("h", "2"); final HPackHeader h3 = new HPackHeader("h", "3"); final HPackHeader h4 = new HPackHeader("h", "4"); for (int i = 0; i < 5; i++) { Assertions.assertEquals(0, fifoLinkedList.size()); Assertions.assertSame(null, fifoLinkedList.getFirst()); Assertions.assertSame(null, fifoLinkedList.getLast()); fifoLinkedList.addFirst(h1); Assertions.assertEquals(1, fifoLinkedList.size()); Assertions.assertSame(h1, fifoLinkedList.getFirst()); Assertions.assertSame(h1, fifoLinkedList.getLast()); fifoLinkedList.addFirst(h2); Assertions.assertEquals(2, fifoLinkedList.size()); Assertions.assertSame(h2, fifoLinkedList.getFirst()); Assertions.assertSame(h1, fifoLinkedList.getLast()); fifoLinkedList.addFirst(h3); Assertions.assertEquals(3, fifoLinkedList.size()); Assertions.assertSame(h3, fifoLinkedList.getFirst()); Assertions.assertSame(h1, fifoLinkedList.getLast()); fifoLinkedList.addFirst(h4); Assertions.assertEquals(4, fifoLinkedList.size()); Assertions.assertSame(h4, fifoLinkedList.getFirst()); Assertions.assertSame(h1, fifoLinkedList.getLast()); fifoLinkedList.removeLast(); Assertions.assertEquals(3, fifoLinkedList.size()); Assertions.assertSame(h4, fifoLinkedList.getFirst()); Assertions.assertSame(h2, fifoLinkedList.getLast()); fifoLinkedList.removeLast(); Assertions.assertEquals(2, fifoLinkedList.size()); Assertions.assertSame(h4, fifoLinkedList.getFirst()); Assertions.assertSame(h3, fifoLinkedList.getLast()); fifoLinkedList.removeLast(); Assertions.assertEquals(1, fifoLinkedList.size()); Assertions.assertSame(h4, fifoLinkedList.getFirst()); Assertions.assertSame(h4, fifoLinkedList.getLast()); fifoLinkedList.removeLast(); Assertions.assertEquals(0, fifoLinkedList.size()); Assertions.assertSame(null, fifoLinkedList.getFirst()); Assertions.assertSame(null, fifoLinkedList.getLast()); } } @Test void testGetIndex() { final FifoLinkedList fifoLinkedList = new FifoLinkedList(); final HPackHeader h1 = new HPackHeader("h", "1"); final HPackHeader h2 = new HPackHeader("h", "2"); final HPackHeader h3 = new HPackHeader("h", "3"); final HPackHeader h4 = new HPackHeader("h", "4"); final FifoLinkedList.InternalNode node1 = fifoLinkedList.addFirst(h1); final FifoLinkedList.InternalNode node2 = fifoLinkedList.addFirst(h2); final FifoLinkedList.InternalNode node3 = fifoLinkedList.addFirst(h3); final FifoLinkedList.InternalNode node4 = fifoLinkedList.addFirst(h4); Assertions.assertEquals(0, fifoLinkedList.getIndex(node4)); Assertions.assertEquals(1, fifoLinkedList.getIndex(node3)); Assertions.assertEquals(2, fifoLinkedList.getIndex(node2)); Assertions.assertEquals(3, fifoLinkedList.getIndex(node1)); Assertions.assertEquals(4, fifoLinkedList.size()); Assertions.assertSame(h4, fifoLinkedList.get(0)); Assertions.assertSame(h3, fifoLinkedList.get(1)); Assertions.assertSame(h2, fifoLinkedList.get(2)); Assertions.assertSame(h1, fifoLinkedList.get(3)); fifoLinkedList.removeLast(); Assertions.assertEquals(0, fifoLinkedList.getIndex(node4)); Assertions.assertEquals(1, fifoLinkedList.getIndex(node3)); Assertions.assertEquals(2, fifoLinkedList.getIndex(node2)); Assertions.assertEquals(-1, fifoLinkedList.getIndex(node1)); Assertions.assertEquals(3, fifoLinkedList.size()); Assertions.assertSame(h4, fifoLinkedList.get(0)); Assertions.assertSame(h3, fifoLinkedList.get(1)); Assertions.assertSame(h2, fifoLinkedList.get(2)); fifoLinkedList.removeLast(); Assertions.assertEquals(0, fifoLinkedList.getIndex(node4)); Assertions.assertEquals(1, fifoLinkedList.getIndex(node3)); Assertions.assertEquals(-1, fifoLinkedList.getIndex(node2)); Assertions.assertEquals(-1, fifoLinkedList.getIndex(node1)); Assertions.assertEquals(2, fifoLinkedList.size()); Assertions.assertSame(h4, fifoLinkedList.get(0)); Assertions.assertSame(h3, fifoLinkedList.get(1)); fifoLinkedList.removeLast(); Assertions.assertEquals(0, fifoLinkedList.getIndex(node4)); Assertions.assertEquals(-1, fifoLinkedList.getIndex(node3)); Assertions.assertEquals(-1, fifoLinkedList.getIndex(node2)); Assertions.assertEquals(-1, fifoLinkedList.getIndex(node1)); Assertions.assertEquals(1, fifoLinkedList.size()); Assertions.assertSame(h4, fifoLinkedList.get(0)); fifoLinkedList.removeLast(); Assertions.assertEquals(-1, fifoLinkedList.getIndex(node4)); Assertions.assertEquals(-1, fifoLinkedList.getIndex(node3)); Assertions.assertEquals(-1, fifoLinkedList.getIndex(node2)); Assertions.assertEquals(-1, fifoLinkedList.getIndex(node1)); Assertions.assertEquals(0, fifoLinkedList.size()); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestHPackCoding.java0100644 0000000 0000000 00000164163 00000250600 026074 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; import static org.hamcrest.MatcherAssert.assertThat; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.util.ByteArrayBuffer; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestHPackCoding { @Test void testIntegerEncodingRFC7541Examples() { final ByteArrayBuffer buffer = new ByteArrayBuffer(16); HPackEncoder.encodeInt(buffer, 5, 10, 0x0); Assertions.assertEquals(1, buffer.length()); Assertions.assertEquals(0b00001010, buffer.byteAt(0) & 0xFF); buffer.clear(); HPackEncoder.encodeInt(buffer, 5, 1337, 0x0); Assertions.assertEquals(3, buffer.length()); Assertions.assertEquals(0b00011111, buffer.byteAt(0) & 0xFF); Assertions.assertEquals(0b10011010, buffer.byteAt(1) & 0xFF); Assertions.assertEquals(0b00001010, buffer.byteAt(2) & 0xFF); buffer.clear(); HPackEncoder.encodeInt(buffer, 8, 42, 0x0); Assertions.assertEquals(1, buffer.length()); Assertions.assertEquals(0b00101010, buffer.byteAt(0) & 0xFF); } static ByteBuffer wrap(final ByteArrayBuffer src) { // Use buffers with array offsets to verify correcness in additional cases final byte[] originalArray = src.array(); final byte[] newArray = new byte[originalArray.length + 2]; System.arraycopy(originalArray, 0, newArray, 1, src.length()); return ByteBuffer.wrap(newArray, 1, src.length()).slice(); } private static byte[] toArray(final ByteBuffer buffer) { final byte[] result = new byte[buffer.remaining()]; buffer.get(result); return result; } @Test void testIntegerCoding() throws Exception { final ByteArrayBuffer buffer = new ByteArrayBuffer(16); for (int n = 4; n <= 8; n++) { buffer.clear(); HPackEncoder.encodeInt(buffer, n, 10, 0x0); Assertions.assertEquals(10, HPackDecoder.decodeInt(wrap(buffer), n)); buffer.clear(); HPackEncoder.encodeInt(buffer, n, 123456, 0x0); Assertions.assertEquals(123456, HPackDecoder.decodeInt(wrap(buffer), n)); buffer.clear(); HPackEncoder.encodeInt(buffer, n, Integer.MAX_VALUE, 0x0); Assertions.assertEquals(Integer.MAX_VALUE, HPackDecoder.decodeInt(wrap(buffer), n)); } } @Test void testIntegerCodingLimit() throws Exception { final ByteBuffer src1 = createByteBuffer(0x7f, 0x80, 0xff, 0xff, 0xff, 0x07); Assertions.assertEquals(Integer.MAX_VALUE, HPackDecoder.decodeInt(src1, 7)); final ByteBuffer src2 = createByteBuffer(0x7f, 0x80, 0xff, 0xff, 0xff, 0x08); try { HPackDecoder.decodeInt(src2, 7); } catch (final HPackException expected) { } final ByteBuffer src3 = createByteBuffer(0x7f, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01); try { HPackDecoder.decodeInt(src3, 7); } catch (final HPackException expected) { } } private static ByteBuffer createByteBuffer(final int... bytes) { final ByteBuffer buffer = ByteBuffer.allocate(bytes.length); for (final int b : bytes) { buffer.put((byte) b); } buffer.flip(); return buffer; } @Test void testPlainStringDecoding() throws Exception { final ByteBuffer src = createByteBuffer( 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79); final ByteArrayBuffer buffer = new ByteArrayBuffer(16); HPackDecoder.decodePlainString(buffer, src); Assertions.assertEquals("custom-key", new String(buffer.array(), 0, buffer.length(), StandardCharsets.US_ASCII)); Assertions.assertFalse(src.hasRemaining(), "Decoding completed"); } @Test void testPlainStringDecodingRemainingContent() throws Exception { final ByteBuffer src = createByteBuffer( 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x01, 0x01, 0x01, 0x01); final ByteArrayBuffer buffer = new ByteArrayBuffer(16); HPackDecoder.decodePlainString(buffer, src); Assertions.assertEquals("custom-key", new String(buffer.array(), 0, buffer.length(), StandardCharsets.US_ASCII)); Assertions.assertEquals(4, src.remaining()); } @Test void testPlainStringDecodingReadOnly() throws Exception { final ByteBuffer src = createByteBuffer( 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x50, 0x50, 0x50, 0x50); final ByteBuffer srcRO = src.asReadOnlyBuffer(); final ByteArrayBuffer buffer = new ByteArrayBuffer(16); HPackDecoder.decodePlainString(buffer, srcRO); Assertions.assertEquals("custom-key", new String(buffer.array(), 0, buffer.length(), StandardCharsets.US_ASCII)); Assertions.assertEquals(4, srcRO.remaining()); } @Test void testPlainStringDecodingTruncated() { final ByteBuffer src = createByteBuffer( 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65); final ByteArrayBuffer buffer = new ByteArrayBuffer(16); Assertions.assertThrows(HPackException.class, () -> HPackDecoder.decodePlainString(buffer, src)); } @Test void testHuffmanDecodingRFC7541Examples() throws Exception { final ByteBuffer src = createByteBuffer( 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff); final ByteArrayBuffer buffer = new ByteArrayBuffer(16); HPackDecoder.decodeHuffman(buffer, src); Assertions.assertEquals("www.example.com", new String(buffer.array(), 0, buffer.length(), StandardCharsets.US_ASCII)); Assertions.assertFalse(src.hasRemaining(), "Decoding completed"); } private static ByteBuffer createByteBuffer(final String s, final Charset charset) { return ByteBuffer.wrap(s.getBytes(charset)); } @Test void testHuffmanEncoding() { final ByteArrayBuffer buffer = new ByteArrayBuffer(16); HPackEncoder.encodeHuffman(buffer, createByteBuffer("www.example.com", StandardCharsets.US_ASCII)); final ByteBuffer expected = createByteBuffer( 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff); Assertions.assertArrayEquals(toArray(expected), buffer.toByteArray()); } @Test void testBasicStringCoding() throws Exception { final HPackEncoder encoder = new HPackEncoder(Integer.MAX_VALUE, StandardCharsets.US_ASCII); final HPackDecoder decoder = new HPackDecoder(Integer.MAX_VALUE, StandardCharsets.US_ASCII); final ByteArrayBuffer buffer = new ByteArrayBuffer(16); encoder.encodeString(buffer, "this and that", false); final StringBuilder strBuf = new StringBuilder(); decoder.decodeString(wrap(buffer), strBuf); Assertions.assertEquals("this and that", strBuf.toString()); buffer.clear(); strBuf.setLength(0); encoder.encodeString(buffer, "this and that and Huffman", true); decoder.decodeString(wrap(buffer), strBuf); Assertions.assertEquals("this and that and Huffman", strBuf.toString()); } @Test void testEnsureCapacity() throws Exception { final HPackEncoder encoder = new HPackEncoder(Integer.MAX_VALUE, StandardCharsets.US_ASCII); final HPackDecoder decoder = new HPackDecoder(Integer.MAX_VALUE, StandardCharsets.UTF_8); final ByteArrayBuffer buffer = new ByteArrayBuffer(16); encoder.encodeString(buffer, "this and that", false); final StringBuilder strBuf = new StringBuilder(); for (int i = 0; i < 1000; i++) { decoder.decodeString(wrap(buffer), strBuf); strBuf.delete(0,strBuf.length()); } Assertions.assertEquals(256, decoder.getTmpBufSize()); } static final int SWISS_GERMAN_HELLO[] = { 0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4 }; static final int RUSSIAN_HELLO[] = { 0x412, 0x441, 0x435, 0x43C, 0x5F, 0x43F, 0x440, 0x438, 0x432, 0x435, 0x442 }; private static String constructHelloString(final int[] raw, final int n) { final StringBuilder buffer = new StringBuilder(); for (int j = 0; j < n; j++) { if (j > 0) { buffer.append("; "); } for (int i = 0; i < raw.length; i++) { buffer.append((char) raw[i]); } } return buffer.toString(); } @Test void testComplexStringCoding1() throws Exception { for (final Charset charset : new Charset[]{StandardCharsets.ISO_8859_1, StandardCharsets.UTF_8, StandardCharsets.UTF_16}) { final ByteArrayBuffer buffer = new ByteArrayBuffer(16); final StringBuilder strBuf = new StringBuilder(); final HPackEncoder encoder = new HPackEncoder(Integer.MAX_VALUE, charset); final HPackDecoder decoder = new HPackDecoder(Integer.MAX_VALUE, charset); for (int n = 0; n < 10; n++) { final String hello = constructHelloString(SWISS_GERMAN_HELLO, 1 + 10 * n); for (final boolean b : new boolean[]{false, true}) { buffer.clear(); encoder.encodeString(buffer, hello, b); strBuf.setLength(0); decoder.decodeString(wrap(buffer), strBuf); final String helloBack = strBuf.toString(); Assertions.assertEquals(hello, helloBack, "charset: " + charset + "; huffman: " + b); } } } } @Test void testComplexStringCoding2() throws Exception { for (final Charset charset : new Charset[]{Charset.forName("KOI8-R"), StandardCharsets.UTF_8, StandardCharsets.UTF_16}) { final ByteArrayBuffer buffer = new ByteArrayBuffer(16); final StringBuilder strBuf = new StringBuilder(); final HPackEncoder encoder = new HPackEncoder(Integer.MAX_VALUE, charset); final HPackDecoder decoder = new HPackDecoder(Integer.MAX_VALUE, charset); for (int n = 0; n < 10; n++) { final String hello = constructHelloString(RUSSIAN_HELLO, 1 + 10 * n); for (final boolean b : new boolean[]{false, true}) { buffer.clear(); strBuf.setLength(0); encoder.encodeString(buffer, hello, b); decoder.decodeString(wrap(buffer), strBuf); final String helloBack = strBuf.toString(); Assertions.assertEquals(hello, helloBack, "charset: " + charset + "; huffman: " + b); } } } } private static void assertHeaderEquals(final Header expected, final Header actual) { Assertions.assertNotNull(actual); Assertions.assertEquals(expected.getName(), actual.getName(), "Header name"); Assertions.assertEquals(expected.getValue(), actual.getValue(), "Header value"); Assertions.assertEquals(expected.isSensitive(), actual.isSensitive(), "Header sensitive flag"); } @Test void testLiteralHeaderWithIndexingDecodingRFC7541Examples() throws Exception { final ByteBuffer src = createByteBuffer( 0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72); final InboundDynamicTable dynamicTable = new InboundDynamicTable(); final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.US_ASCII); final Header header = decoder.decodeLiteralHeader(src, HPackRepresentation.WITH_INDEXING); assertHeaderEquals(new BasicHeader("custom-key", "custom-header"), header); Assertions.assertFalse(src.hasRemaining(), "Decoding completed"); Assertions.assertEquals(1, dynamicTable.dynamicLength()); assertHeaderEquals(header, dynamicTable.getDynamicEntry(0)); } @Test void testLiteralHeaderWithoutIndexingDecodingRFC7541Examples() throws Exception { final ByteBuffer src = createByteBuffer( 0x04, 0x0c, 0x2f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68); final InboundDynamicTable dynamicTable = new InboundDynamicTable(); final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.US_ASCII); final Header header = decoder.decodeLiteralHeader(src, HPackRepresentation.WITHOUT_INDEXING); assertHeaderEquals(new BasicHeader(":path", "/sample/path"), header); Assertions.assertFalse(src.hasRemaining(), "Decoding completed"); Assertions.assertEquals(0, dynamicTable.dynamicLength()); } @Test void testLiteralHeaderNeverIndexedDecodingRFC7541Examples() throws Exception { final ByteBuffer src = createByteBuffer( 0x10, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74); final InboundDynamicTable dynamicTable = new InboundDynamicTable(); final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.US_ASCII); final Header header = decoder.decodeLiteralHeader(src, HPackRepresentation.NEVER_INDEXED); assertHeaderEquals(new BasicHeader("password", "secret", true), header); Assertions.assertFalse(src.hasRemaining(), "Decoding completed"); Assertions.assertEquals(0, dynamicTable.dynamicLength()); } @Test void testIndexedHeaderDecodingRFC7541Examples() throws Exception { final ByteBuffer src = createByteBuffer(0x82); final InboundDynamicTable dynamicTable = new InboundDynamicTable(); final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.US_ASCII); final Header header = decoder.decodeIndexedHeader(src); assertHeaderEquals(new BasicHeader(":method", "GET"), header); Assertions.assertFalse(src.hasRemaining(), "Decoding completed"); Assertions.assertEquals(0, dynamicTable.dynamicLength()); } @Test void testRequestDecodingWithoutHuffmanRFC7541Examples() throws Exception { final ByteBuffer src1 = createByteBuffer( 0x82, 0x86, 0x84, 0x41, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d); final InboundDynamicTable dynamicTable = new InboundDynamicTable(); final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.US_ASCII); final List

headers1 = decoder.decodeHeaders(src1); Assertions.assertEquals(4, headers1.size()); assertHeaderEquals(new BasicHeader(":method", "GET"), headers1.get(0)); assertHeaderEquals(new BasicHeader(":scheme", "http"), headers1.get(1)); assertHeaderEquals(new BasicHeader(":path", "/"), headers1.get(2)); assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), headers1.get(3)); Assertions.assertEquals(1, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(0)); Assertions.assertEquals(57, dynamicTable.getCurrentSize()); final ByteBuffer src2 = createByteBuffer( 0x82, 0x86, 0x84, 0xbe, 0x58, 0x08, 0x6e, 0x6f, 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65); final List
headers2 = decoder.decodeHeaders(src2); Assertions.assertEquals(5, headers2.size()); assertHeaderEquals(new BasicHeader(":method", "GET"), headers2.get(0)); assertHeaderEquals(new BasicHeader(":scheme", "http"), headers2.get(1)); assertHeaderEquals(new BasicHeader(":path", "/"), headers2.get(2)); assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), headers2.get(3)); assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), headers2.get(4)); Assertions.assertEquals(2, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(1)); Assertions.assertEquals(110, dynamicTable.getCurrentSize()); final ByteBuffer src3 = createByteBuffer( 0x82, 0x87, 0x85, 0xbf, 0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65); final List
headers3 = decoder.decodeHeaders(src3); Assertions.assertEquals(5, headers3.size()); assertHeaderEquals(new BasicHeader(":method", "GET"), headers3.get(0)); assertHeaderEquals(new BasicHeader(":scheme", "https"), headers3.get(1)); assertHeaderEquals(new BasicHeader(":path", "/index.html"), headers3.get(2)); assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), headers3.get(3)); assertHeaderEquals(new BasicHeader("custom-key", "custom-value"), headers3.get(4)); Assertions.assertEquals(3, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader("custom-key", "custom-value"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), dynamicTable.getDynamicEntry(1)); assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(2)); Assertions.assertEquals(164, dynamicTable.getCurrentSize()); } @Test void testRequestDecodingWithHuffmanRFC7541Examples() throws Exception { final ByteBuffer src1 = createByteBuffer( 0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff); final InboundDynamicTable dynamicTable = new InboundDynamicTable(); final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.US_ASCII); final List
headers1 = decoder.decodeHeaders(src1); Assertions.assertEquals(4, headers1.size()); assertHeaderEquals(new BasicHeader(":method", "GET"), headers1.get(0)); assertHeaderEquals(new BasicHeader(":scheme", "http"), headers1.get(1)); assertHeaderEquals(new BasicHeader(":path", "/"), headers1.get(2)); assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), headers1.get(3)); Assertions.assertEquals(1, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(0)); Assertions.assertEquals(57, dynamicTable.getCurrentSize()); final ByteBuffer src2 = createByteBuffer( 0x82, 0x86, 0x84, 0xbe, 0x58, 0x86, 0xa8, 0xeb, 0x10, 0x64, 0x9c, 0xbf); final List
headers2 = decoder.decodeHeaders(src2); Assertions.assertEquals(5, headers2.size()); assertHeaderEquals(new BasicHeader(":method", "GET"), headers2.get(0)); assertHeaderEquals(new BasicHeader(":scheme", "http"), headers2.get(1)); assertHeaderEquals(new BasicHeader(":path", "/"), headers2.get(2)); assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), headers2.get(3)); assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), headers2.get(4)); Assertions.assertEquals(2, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(1)); Assertions.assertEquals(110, dynamicTable.getCurrentSize()); final ByteBuffer src3 = createByteBuffer( 0x82, 0x87, 0x85, 0xbf, 0x40, 0x88, 0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xa9, 0x7d, 0x7f, 0x89, 0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xb8, 0xe8, 0xb4, 0xbf); final List
headers3 = decoder.decodeHeaders(src3); Assertions.assertEquals(5, headers3.size()); assertHeaderEquals(new BasicHeader(":method", "GET"), headers3.get(0)); assertHeaderEquals(new BasicHeader(":scheme", "https"), headers3.get(1)); assertHeaderEquals(new BasicHeader(":path", "/index.html"), headers3.get(2)); assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), headers3.get(3)); assertHeaderEquals(new BasicHeader("custom-key", "custom-value"), headers3.get(4)); Assertions.assertEquals(3, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader("custom-key", "custom-value"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), dynamicTable.getDynamicEntry(1)); assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(2)); Assertions.assertEquals(164, dynamicTable.getCurrentSize()); } @Test void testResponseDecodingWithoutHuffmanRFC7541Examples() throws Exception { final ByteBuffer src1 = createByteBuffer( 0x48, 0x03, 0x33, 0x30, 0x32, 0x58, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x61, 0x1d, 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, 0x31, 0x20, 0x47, 0x4d, 0x54, 0x6e, 0x17, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d); final InboundDynamicTable dynamicTable = new InboundDynamicTable(); dynamicTable.setMaxSize(256); final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.US_ASCII); final List
headers1 = decoder.decodeHeaders(src1); Assertions.assertEquals(4, headers1.size()); assertHeaderEquals(new BasicHeader(":status", "302"), headers1.get(0)); assertHeaderEquals(new BasicHeader("cache-control", "private"), headers1.get(1)); assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), headers1.get(2)); assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), headers1.get(3)); Assertions.assertEquals(4, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), dynamicTable.getDynamicEntry(1)); assertHeaderEquals(new BasicHeader("cache-control", "private"), dynamicTable.getDynamicEntry(2)); assertHeaderEquals(new BasicHeader(":status", "302"), dynamicTable.getDynamicEntry(3)); Assertions.assertEquals(222, dynamicTable.getCurrentSize()); final ByteBuffer src2 = createByteBuffer( 0x48, 0x03, 0x33, 0x30, 0x37, 0xc1, 0xc0, 0xbf); final List
headers2 = decoder.decodeHeaders(src2); Assertions.assertEquals(4, headers2.size()); assertHeaderEquals(new BasicHeader(":status", "307"), headers2.get(0)); assertHeaderEquals(new BasicHeader("cache-control", "private"), headers2.get(1)); assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), headers2.get(2)); assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), headers2.get(3)); Assertions.assertEquals(4, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader(":status", "307"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), dynamicTable.getDynamicEntry(1)); assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), dynamicTable.getDynamicEntry(2)); assertHeaderEquals(new BasicHeader("cache-control", "private"), dynamicTable.getDynamicEntry(3)); Assertions.assertEquals(222, dynamicTable.getCurrentSize()); final ByteBuffer src3 = createByteBuffer( 0x88, 0xc1, 0x61, 0x1d, 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, 0x32, 0x20, 0x47, 0x4d, 0x54, 0xc0, 0x5a, 0x04, 0x67, 0x7a, 0x69, 0x70, 0x77, 0x38, 0x66, 0x6f, 0x6f, 0x3d, 0x41, 0x53, 0x44, 0x4a, 0x4b, 0x48, 0x51, 0x4b, 0x42, 0x5a, 0x58, 0x4f, 0x51, 0x57, 0x45, 0x4f, 0x50, 0x49, 0x55, 0x41, 0x58, 0x51, 0x57, 0x45, 0x4f, 0x49, 0x55, 0x3b, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, 0x3d, 0x33, 0x36, 0x30, 0x30, 0x3b, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x31); final List
headers3 = decoder.decodeHeaders(src3); Assertions.assertEquals(6, headers3.size()); assertHeaderEquals(new BasicHeader(":status", "200"), headers3.get(0)); assertHeaderEquals(new BasicHeader("cache-control", "private"), headers3.get(1)); assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:22 GMT"), headers3.get(2)); assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), headers3.get(3)); assertHeaderEquals(new BasicHeader("content-encoding", "gzip"), headers3.get(4)); assertHeaderEquals(new BasicHeader("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"), headers3.get(5)); Assertions.assertEquals(3, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader("content-encoding", "gzip"), dynamicTable.getDynamicEntry(1)); assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:22 GMT"), dynamicTable.getDynamicEntry(2)); Assertions.assertEquals(215, dynamicTable.getCurrentSize()); } @Test void testResponseDecodingWithHuffmanRFC7541Examples() throws Exception { final ByteBuffer src1 = createByteBuffer( 0x48, 0x82, 0x64, 0x02, 0x58, 0x85, 0xae, 0xc3, 0x77, 0x1a, 0x4b, 0x61, 0x96, 0xd0, 0x7a, 0xbe, 0x94, 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 0x95, 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x82, 0xa6, 0x2d, 0x1b, 0xff, 0x6e, 0x91, 0x9d, 0x29, 0xad, 0x17, 0x18, 0x63, 0xc7, 0x8f, 0x0b, 0x97, 0xc8, 0xe9, 0xae, 0x82, 0xae, 0x43, 0xd3); final InboundDynamicTable dynamicTable = new InboundDynamicTable(); dynamicTable.setMaxSize(256); final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.US_ASCII); final List
headers1 = decoder.decodeHeaders(src1); Assertions.assertEquals(4, headers1.size()); assertHeaderEquals(new BasicHeader(":status", "302"), headers1.get(0)); assertHeaderEquals(new BasicHeader("cache-control", "private"), headers1.get(1)); assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), headers1.get(2)); assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), headers1.get(3)); Assertions.assertEquals(4, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), dynamicTable.getDynamicEntry(1)); assertHeaderEquals(new BasicHeader("cache-control", "private"), dynamicTable.getDynamicEntry(2)); assertHeaderEquals(new BasicHeader(":status", "302"), dynamicTable.getDynamicEntry(3)); Assertions.assertEquals(222, dynamicTable.getCurrentSize()); final ByteBuffer src2 = createByteBuffer( 0x48, 0x83, 0x64, 0x0e, 0xff, 0xc1, 0xc0, 0xbf); final List
headers2 = decoder.decodeHeaders(src2); Assertions.assertEquals(4, headers2.size()); assertHeaderEquals(new BasicHeader(":status", "307"), headers2.get(0)); assertHeaderEquals(new BasicHeader("cache-control", "private"), headers2.get(1)); assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), headers2.get(2)); assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), headers2.get(3)); Assertions.assertEquals(4, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader(":status", "307"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), dynamicTable.getDynamicEntry(1)); assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), dynamicTable.getDynamicEntry(2)); assertHeaderEquals(new BasicHeader("cache-control", "private"), dynamicTable.getDynamicEntry(3)); Assertions.assertEquals(222, dynamicTable.getCurrentSize()); final ByteBuffer src3 = createByteBuffer( 0x88, 0xc1, 0x61, 0x96, 0xd0, 0x7a, 0xbe, 0x94, 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 0x95, 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x84, 0xa6, 0x2d, 0x1b, 0xff, 0xc0, 0x5a, 0x83, 0x9b, 0xd9, 0xab, 0x77, 0xad, 0x94, 0xe7, 0x82, 0x1d, 0xd7, 0xf2, 0xe6, 0xc7, 0xb3, 0x35, 0xdf, 0xdf, 0xcd, 0x5b, 0x39, 0x60, 0xd5, 0xaf, 0x27, 0x08, 0x7f, 0x36, 0x72, 0xc1, 0xab, 0x27, 0x0f, 0xb5, 0x29, 0x1f, 0x95, 0x87, 0x31, 0x60, 0x65, 0xc0, 0x03, 0xed, 0x4e, 0xe5, 0xb1, 0x06, 0x3d, 0x50, 0x07); final List
headers3 = decoder.decodeHeaders(src3); Assertions.assertEquals(6, headers3.size()); assertHeaderEquals(new BasicHeader(":status", "200"), headers3.get(0)); assertHeaderEquals(new BasicHeader("cache-control", "private"), headers3.get(1)); assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:22 GMT"), headers3.get(2)); assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), headers3.get(3)); assertHeaderEquals(new BasicHeader("content-encoding", "gzip"), headers3.get(4)); assertHeaderEquals(new BasicHeader("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"), headers3.get(5)); Assertions.assertEquals(3, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader("content-encoding", "gzip"), dynamicTable.getDynamicEntry(1)); assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:22 GMT"), dynamicTable.getDynamicEntry(2)); Assertions.assertEquals(215, dynamicTable.getCurrentSize()); } private static byte[] createByteArray(final int... bytes) { final byte[] buffer = new byte[bytes.length]; for (int i = 0; i < bytes.length; i++) { buffer[i] = (byte) bytes[i]; } return buffer; } @Test void testLiteralHeaderWithIndexingEncodingRFC7541Examples() throws Exception { final OutboundDynamicTable dynamicTable = new OutboundDynamicTable(); final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII); final ByteArrayBuffer buf = new ByteArrayBuffer(128); final Header header = new BasicHeader("custom-key", "custom-header"); encoder.encodeLiteralHeader(buf, null, header, HPackRepresentation.WITH_INDEXING, false); final byte[] expected = createByteArray( 0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72); Assertions.assertArrayEquals(expected, buf.toByteArray()); } @Test void testLiteralHeaderWithoutIndexingEncodingRFC7541Examples() throws Exception { final OutboundDynamicTable dynamicTable = new OutboundDynamicTable(); final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII); final ByteArrayBuffer buf = new ByteArrayBuffer(128); final Header header = new BasicHeader(":path", "/sample/path"); encoder.encodeLiteralHeader(buf, new HPackEntry() { @Override public int getIndex() { return 4; } @Override public HPackHeader getHeader() { return new HPackHeader(header); } }, header, HPackRepresentation.WITHOUT_INDEXING, false); final byte[] expected = createByteArray( 0x04, 0x0c, 0x2f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68); Assertions.assertArrayEquals(expected, buf.toByteArray()); } @Test void testLiteralHeaderNeverIndexedEncodingRFC7541Examples() throws Exception { final OutboundDynamicTable dynamicTable = new OutboundDynamicTable(); final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII); final ByteArrayBuffer buf = new ByteArrayBuffer(128); final Header header = new BasicHeader("password", "secret", true); encoder.encodeLiteralHeader(buf, null, header, HPackRepresentation.NEVER_INDEXED, false); final byte[] expected = createByteArray( 0x10, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74); Assertions.assertArrayEquals(expected, buf.toByteArray()); } @Test void testIndexedHeaderEncodingRFC7541Examples() { final OutboundDynamicTable dynamicTable = new OutboundDynamicTable(); final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII); final ByteArrayBuffer buf = new ByteArrayBuffer(128); encoder.encodeIndex(buf, 2); final byte[] expected = createByteArray(0x82); Assertions.assertArrayEquals(expected, buf.toByteArray()); } @Test void testRequestEncodingWithoutHuffmanRFC7541Examples() throws Exception { final OutboundDynamicTable dynamicTable = new OutboundDynamicTable(); final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII); final ByteArrayBuffer buf = new ByteArrayBuffer(256); final List
headers1 = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader(":path", "/"), new BasicHeader(":authority", "www.example.com")); encoder.encodeHeaders(buf, headers1, false, false); final byte[] expected1 = createByteArray( 0x82, 0x86, 0x84, 0x41, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d); Assertions.assertArrayEquals(expected1, buf.toByteArray()); Assertions.assertEquals(1, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(0)); Assertions.assertEquals(57, dynamicTable.getCurrentSize()); final List
headers2 = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader(":path", "/"), new BasicHeader(":authority", "www.example.com"), new BasicHeader("cache-control", "no-cache")); buf.clear(); encoder.encodeHeaders(buf, headers2, false, false); final byte[] expected2 = createByteArray( 0x82, 0x86, 0x84, 0xbe, 0x58, 0x08, 0x6e, 0x6f, 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65); Assertions.assertArrayEquals(expected2, buf.toByteArray()); Assertions.assertEquals(2, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(1)); Assertions.assertEquals(110, dynamicTable.getCurrentSize()); final List
headers3 = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "https"), new BasicHeader(":path", "/index.html"), new BasicHeader(":authority", "www.example.com"), new BasicHeader("custom-key", "custom-value")); buf.clear(); encoder.encodeHeaders(buf, headers3, false, false); final byte[] expected3 = createByteArray( 0x82, 0x87, 0x85, 0xbf, 0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65); Assertions.assertArrayEquals(expected3, buf.toByteArray()); Assertions.assertEquals(3, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader("custom-key", "custom-value"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), dynamicTable.getDynamicEntry(1)); assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(2)); Assertions.assertEquals(164, dynamicTable.getCurrentSize()); } @Test void testRequestEncodingWithHuffmanRFC7541Examples() throws Exception { final OutboundDynamicTable dynamicTable = new OutboundDynamicTable(); final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII); final ByteArrayBuffer buf = new ByteArrayBuffer(256); final List
headers1 = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader(":path", "/"), new BasicHeader(":authority", "www.example.com")); encoder.encodeHeaders(buf, headers1, false, true); final byte[] expected1 = createByteArray( 0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff); Assertions.assertArrayEquals(expected1, buf.toByteArray()); Assertions.assertEquals(1, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(0)); Assertions.assertEquals(57, dynamicTable.getCurrentSize()); final List
headers2 = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader(":path", "/"), new BasicHeader(":authority", "www.example.com"), new BasicHeader("cache-control", "no-cache")); buf.clear(); encoder.encodeHeaders(buf, headers2, false, true); final byte[] expected2 = createByteArray( 0x82, 0x86, 0x84, 0xbe, 0x58, 0x86, 0xa8, 0xeb, 0x10, 0x64, 0x9c, 0xbf); Assertions.assertArrayEquals(expected2, buf.toByteArray()); Assertions.assertEquals(2, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(1)); Assertions.assertEquals(110, dynamicTable.getCurrentSize()); final List
headers3 = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "https"), new BasicHeader(":path", "/index.html"), new BasicHeader(":authority", "www.example.com"), new BasicHeader("custom-key", "custom-value")); buf.clear(); encoder.encodeHeaders(buf, headers3, false, true); final byte[] expected3 = createByteArray( 0x82, 0x87, 0x85, 0xbf, 0x40, 0x88, 0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xa9, 0x7d, 0x7f, 0x89, 0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xb8, 0xe8, 0xb4, 0xbf); Assertions.assertArrayEquals(expected3, buf.toByteArray()); Assertions.assertEquals(3, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader("custom-key", "custom-value"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader("cache-control", "no-cache"), dynamicTable.getDynamicEntry(1)); assertHeaderEquals(new BasicHeader(":authority", "www.example.com"), dynamicTable.getDynamicEntry(2)); Assertions.assertEquals(164, dynamicTable.getCurrentSize()); } @Test void testResponseEncodingWithoutHuffmanRFC7541Examples() throws Exception { final OutboundDynamicTable dynamicTable = new OutboundDynamicTable(); dynamicTable.setMaxSize(256); final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII); final ByteArrayBuffer buf = new ByteArrayBuffer(256); final List
headers1 = Arrays.asList( new BasicHeader(":status", "302"), new BasicHeader("cache-control", "private"), new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), new BasicHeader("location", "https://www.example.com")); encoder.encodeHeaders(buf, headers1, false, false); final byte[] expected1 = createByteArray( 0x48, 0x03, 0x33, 0x30, 0x32, 0x58, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x61, 0x1d, 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, 0x31, 0x20, 0x47, 0x4d, 0x54, 0x6e, 0x17, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d); Assertions.assertArrayEquals(expected1, buf.toByteArray()); Assertions.assertEquals(4, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), dynamicTable.getDynamicEntry(1)); assertHeaderEquals(new BasicHeader("cache-control", "private"), dynamicTable.getDynamicEntry(2)); assertHeaderEquals(new BasicHeader(":status", "302"), dynamicTable.getDynamicEntry(3)); Assertions.assertEquals(222, dynamicTable.getCurrentSize()); final List
headers2 = Arrays.asList( new BasicHeader(":status", "307"), new BasicHeader("cache-control", "private"), new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), new BasicHeader("location", "https://www.example.com")); buf.clear(); encoder.encodeHeaders(buf, headers2, false, false); final byte[] expected2 = createByteArray( 0x48, 0x03, 0x33, 0x30, 0x37, 0xc1, 0xc0, 0xbf); Assertions.assertArrayEquals(expected2, buf.toByteArray()); Assertions.assertEquals(4, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader(":status", "307"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), dynamicTable.getDynamicEntry(1)); assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), dynamicTable.getDynamicEntry(2)); assertHeaderEquals(new BasicHeader("cache-control", "private"), dynamicTable.getDynamicEntry(3)); Assertions.assertEquals(222, dynamicTable.getCurrentSize()); final List
headers3 = Arrays.asList( new BasicHeader(":status", "200"), new BasicHeader("cache-control", "private"), new BasicHeader("date", "Mon, 21 Oct 2013 20:13:22 GMT"), new BasicHeader("location", "https://www.example.com"), new BasicHeader("content-encoding", "gzip"), new BasicHeader("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1")); buf.clear(); encoder.encodeHeaders(buf, headers3, false, false); final byte[] expected3 = createByteArray( 0x88, 0xc1, 0x61, 0x1d, 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, 0x32, 0x20, 0x47, 0x4d, 0x54, 0xc0, 0x5a, 0x04, 0x67, 0x7a, 0x69, 0x70, 0x77, 0x38, 0x66, 0x6f, 0x6f, 0x3d, 0x41, 0x53, 0x44, 0x4a, 0x4b, 0x48, 0x51, 0x4b, 0x42, 0x5a, 0x58, 0x4f, 0x51, 0x57, 0x45, 0x4f, 0x50, 0x49, 0x55, 0x41, 0x58, 0x51, 0x57, 0x45, 0x4f, 0x49, 0x55, 0x3b, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, 0x3d, 0x33, 0x36, 0x30, 0x30, 0x3b, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x31); Assertions.assertArrayEquals(expected3, buf.toByteArray()); Assertions.assertEquals(3, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader("content-encoding", "gzip"), dynamicTable.getDynamicEntry(1)); assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:22 GMT"), dynamicTable.getDynamicEntry(2)); Assertions.assertEquals(215, dynamicTable.getCurrentSize()); } @Test void testResponseEncodingWithHuffmanRFC7541Examples() throws Exception { final OutboundDynamicTable dynamicTable = new OutboundDynamicTable(); dynamicTable.setMaxSize(256); final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII); final ByteArrayBuffer buf = new ByteArrayBuffer(256); final List
headers1 = Arrays.asList( new BasicHeader(":status", "302"), new BasicHeader("cache-control", "private"), new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), new BasicHeader("location", "https://www.example.com")); encoder.encodeHeaders(buf, headers1, false, true); final byte[] expected1 = createByteArray( 0x48, 0x82, 0x64, 0x02, 0x58, 0x85, 0xae, 0xc3, 0x77, 0x1a, 0x4b, 0x61, 0x96, 0xd0, 0x7a, 0xbe, 0x94, 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 0x95, 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x82, 0xa6, 0x2d, 0x1b, 0xff, 0x6e, 0x91, 0x9d, 0x29, 0xad, 0x17, 0x18, 0x63, 0xc7, 0x8f, 0x0b, 0x97, 0xc8, 0xe9, 0xae, 0x82, 0xae, 0x43, 0xd3); Assertions.assertArrayEquals(expected1, buf.toByteArray()); Assertions.assertEquals(4, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), dynamicTable.getDynamicEntry(1)); assertHeaderEquals(new BasicHeader("cache-control", "private"), dynamicTable.getDynamicEntry(2)); assertHeaderEquals(new BasicHeader(":status", "302"), dynamicTable.getDynamicEntry(3)); Assertions.assertEquals(222, dynamicTable.getCurrentSize()); final List
headers2 = Arrays.asList( new BasicHeader(":status", "307"), new BasicHeader("cache-control", "private"), new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), new BasicHeader("location", "https://www.example.com")); buf.clear(); encoder.encodeHeaders(buf, headers2, false, true); final byte[] expected2 = createByteArray( 0x48, 0x83, 0x64, 0x0e, 0xff, 0xc1, 0xc0, 0xbf); Assertions.assertArrayEquals(expected2, buf.toByteArray()); Assertions.assertEquals(4, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader(":status", "307"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader("location", "https://www.example.com"), dynamicTable.getDynamicEntry(1)); assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:21 GMT"), dynamicTable.getDynamicEntry(2)); assertHeaderEquals(new BasicHeader("cache-control", "private"), dynamicTable.getDynamicEntry(3)); Assertions.assertEquals(222, dynamicTable.getCurrentSize()); final List
headers3 = Arrays.asList( new BasicHeader(":status", "200"), new BasicHeader("cache-control", "private"), new BasicHeader("date", "Mon, 21 Oct 2013 20:13:22 GMT"), new BasicHeader("location", "https://www.example.com"), new BasicHeader("content-encoding", "gzip"), new BasicHeader("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1")); buf.clear(); encoder.encodeHeaders(buf, headers3, false, true); final byte[] expected3 = createByteArray( 0x88, 0xc1, 0x61, 0x96, 0xd0, 0x7a, 0xbe, 0x94, 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 0x95, 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x84, 0xa6, 0x2d, 0x1b, 0xff, 0xc0, 0x5a, 0x83, 0x9b, 0xd9, 0xab, 0x77, 0xad, 0x94, 0xe7, 0x82, 0x1d, 0xd7, 0xf2, 0xe6, 0xc7, 0xb3, 0x35, 0xdf, 0xdf, 0xcd, 0x5b, 0x39, 0x60, 0xd5, 0xaf, 0x27, 0x08, 0x7f, 0x36, 0x72, 0xc1, 0xab, 0x27, 0x0f, 0xb5, 0x29, 0x1f, 0x95, 0x87, 0x31, 0x60, 0x65, 0xc0, 0x03, 0xed, 0x4e, 0xe5, 0xb1, 0x06, 0x3d, 0x50, 0x07); Assertions.assertArrayEquals(expected3, buf.toByteArray()); Assertions.assertEquals(3, dynamicTable.dynamicLength()); assertHeaderEquals(new BasicHeader("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"), dynamicTable.getDynamicEntry(0)); assertHeaderEquals(new BasicHeader("content-encoding", "gzip"), dynamicTable.getDynamicEntry(1)); assertHeaderEquals(new BasicHeader("date", "Mon, 21 Oct 2013 20:13:22 GMT"), dynamicTable.getDynamicEntry(2)); Assertions.assertEquals(215, dynamicTable.getCurrentSize()); } @Test void testHeaderEntrySizeNonAscii() throws Exception { final ByteArrayBuffer buffer = new ByteArrayBuffer(128); final Header header = new BasicHeader("hello", constructHelloString(SWISS_GERMAN_HELLO, 1)); final OutboundDynamicTable outboundTable1 = new OutboundDynamicTable(); final HPackEncoder encoder1 = new HPackEncoder(outboundTable1, StandardCharsets.ISO_8859_1); final InboundDynamicTable inboundTable1 = new InboundDynamicTable(); final HPackDecoder decoder1 = new HPackDecoder(inboundTable1, StandardCharsets.ISO_8859_1); encoder1.setMaxTableSize(48); decoder1.setMaxTableSize(48); encoder1.encodeHeader(buffer, header); assertHeaderEquals(header, decoder1.decodeHeader(wrap(buffer))); Assertions.assertEquals(1, outboundTable1.dynamicLength()); Assertions.assertEquals(1, inboundTable1.dynamicLength()); assertHeaderEquals(header, outboundTable1.getDynamicEntry(0)); assertHeaderEquals(header, inboundTable1.getDynamicEntry(0)); buffer.clear(); final OutboundDynamicTable outboundTable2 = new OutboundDynamicTable(); final HPackEncoder encoder2 = new HPackEncoder(outboundTable2, StandardCharsets.UTF_8); final InboundDynamicTable inboundTable2 = new InboundDynamicTable(); final HPackDecoder decoder2 = new HPackDecoder(inboundTable2, StandardCharsets.UTF_8); encoder2.setMaxTableSize(48); decoder2.setMaxTableSize(48); encoder2.encodeHeader(buffer, header); assertHeaderEquals(header, decoder2.decodeHeader(wrap(buffer))); Assertions.assertEquals(0, outboundTable2.dynamicLength()); Assertions.assertEquals(0, inboundTable2.dynamicLength()); } @Test void testHeaderSizeLimit() throws Exception { final HPackEncoder encoder = new HPackEncoder(Integer.MAX_VALUE, StandardCharsets.US_ASCII); final HPackDecoder decoder = new HPackDecoder(Integer.MAX_VALUE, StandardCharsets.US_ASCII); final ByteArrayBuffer buf = new ByteArrayBuffer(128); encoder.encodeHeaders(buf, Arrays.asList( new BasicHeader("regular-header", "blah"), new BasicHeader("big-f-header", "12345678901234567890123456789012345678901234567890" + "123456789012345678901234567890123456789012345678901234567890")), false); assertThat(decoder.decodeHeaders(wrap(buf)).size(), CoreMatchers.equalTo(2)); decoder.setMaxListSize(1000000); assertThat(decoder.decodeHeaders(wrap(buf)).size(), CoreMatchers.equalTo(2)); decoder.setMaxListSize(200); Assertions.assertThrows(HeaderListConstraintException.class, () -> decoder.decodeHeaders(wrap(buf))); } @Test void testHeaderEmptyASCII() throws Exception { final HPackEncoder encoder = new HPackEncoder(Integer.MAX_VALUE, StandardCharsets.US_ASCII); final HPackDecoder decoder = new HPackDecoder(Integer.MAX_VALUE, StandardCharsets.US_ASCII); final ByteArrayBuffer buf = new ByteArrayBuffer(128); final Header header = new BasicHeader("empty-header", ""); encoder.encodeHeader(buf, header); assertHeaderEquals(header, decoder.decodeHeader(wrap(buf))); } @Test void testHeaderEmptyUTF8() throws Exception { final HPackEncoder encoder = new HPackEncoder(Integer.MAX_VALUE, StandardCharsets.UTF_8); final HPackDecoder decoder = new HPackDecoder(Integer.MAX_VALUE, StandardCharsets.UTF_8); final ByteArrayBuffer buf = new ByteArrayBuffer(128); final Header header = new BasicHeader("empty-header", ""); encoder.encodeHeader(buf, header); assertHeaderEquals(header, decoder.decodeHeader(wrap(buf))); } @Test void encoderDynamicHeaderTableMaxSizeNotIncreasedBySettingsFrame() throws Exception { final OutboundDynamicTable dynamicTable = new OutboundDynamicTable(4096); final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.UTF_8); //emulate receiving a settings frame from the receiver encoder.setMaxTableSize(Integer.MAX_VALUE); //actual table size should not change until we are able to send an update to the receiver Assertions.assertEquals(4096, dynamicTable.getMaxSize()); } @Test void encoderDynamicHeaderTableMaxSizeChangeCausesUpdateHeader() throws Exception { final OutboundDynamicTable dynamicTable = new OutboundDynamicTable(4096); final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.UTF_8); //emulate receiving a settings frame from the receiver encoder.setMaxTableSize(8192); final ByteArrayBuffer buf = new ByteArrayBuffer(128); final Header header = new BasicHeader("empty-header", ""); encoder.encodeHeader(buf, header); final int firstByte = buf.byteAt(0); final int masked = firstByte & 0xe0; //first header field is table size update Assertions.assertEquals(0x20, masked); //update has new table size as value Assertions.assertEquals(8192, HPackDecoder.decodeInt(wrap(buf), 5)); } @Test void decoderDynamicHeaderTableMaxSizeNotIncreasedBySettingsFrame() throws Exception { final InboundDynamicTable dynamicTable = new InboundDynamicTable(4096); final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.UTF_8); //emulate something on our side changing the max dynamic table size //this would cause us to send a new settings frame to the sender decoder.setMaxTableSize(Integer.MAX_VALUE); //actual table size should not change until sender sends us a table update Assertions.assertEquals(4096, dynamicTable.getMaxSize()); } @Test void decoderDynamicHeaderTableMaxSizeUpdatesAfter() throws Exception { final InboundDynamicTable dynamicTable = new InboundDynamicTable(4096); final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.UTF_8); decoder.setMaxTableSize(Integer.MAX_VALUE); final ByteArrayBuffer buf = new ByteArrayBuffer(128); HPackEncoder.encodeInt(buf, 5, 8192, 0x20); decoder.decodeHeaders(wrap(buf)); Assertions.assertEquals(8192, dynamicTable.getMaxSize()); } @Test void decoderDynamicHeaderTableMaxSizeLimitedByConfig() throws Exception { final InboundDynamicTable dynamicTable = new InboundDynamicTable(4096); final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.UTF_8); //do not increase max size, this should limit requests from the receiver to increase max size //emulate receiving header that illegally increases the table size above our max final ByteArrayBuffer buf = new ByteArrayBuffer(128); HPackEncoder.encodeInt(buf, 5, 8192, 0x20); Assertions.assertThrows(HPackException.class, () -> decoder.decodeHeaders(wrap(buf))); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestInboundDynamicTable.java0100644 0000000 0000000 00000004716 00000250600 027632 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestInboundDynamicTable { @Test void testBasics() { final InboundDynamicTable table = new InboundDynamicTable(); Assertions.assertEquals(Integer.MAX_VALUE, table.getMaxSize()); Assertions.assertEquals(0, table.getCurrentSize()); final HPackHeader header1 = new HPackHeader("h", "1"); table.add(header1); Assertions.assertEquals(1, table.dynamicLength()); Assertions.assertEquals(61, table.staticLength()); Assertions.assertEquals(62, table.length()); Assertions.assertSame(header1, table.getHeader(62)); Assertions.assertEquals(34, table.getCurrentSize()); } @Test void testEviction() { final InboundDynamicTable table = new InboundDynamicTable(); table.add(new HPackHeader("h", "1")); table.add(new HPackHeader("h", "2")); Assertions.assertEquals(68, table.getCurrentSize()); table.setMaxSize(256); Assertions.assertEquals(68, table.getCurrentSize()); table.setMaxSize(67); Assertions.assertEquals(34, table.getCurrentSize()); table.setMaxSize(10); Assertions.assertEquals(0, table.getCurrentSize()); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestOutboundDynamicTable.java0100644 0000000 0000000 00000004723 00000250600 030031 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.hpack; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestOutboundDynamicTable { @Test void testBasics() { final OutboundDynamicTable table = new OutboundDynamicTable(); Assertions.assertEquals(Integer.MAX_VALUE, table.getMaxSize()); Assertions.assertEquals(0, table.getCurrentSize()); final HPackHeader header1 = new HPackHeader("h", "1"); table.add(header1); Assertions.assertEquals(1, table.dynamicLength()); Assertions.assertEquals(61, table.staticLength()); Assertions.assertEquals(62, table.length()); Assertions.assertSame(header1, table.getHeader(62)); Assertions.assertEquals(34, table.getCurrentSize()); } @Test void testEviction() { final OutboundDynamicTable table = new OutboundDynamicTable(); table.add(new HPackHeader("h", "1")); table.add(new HPackHeader("h", "2")); Assertions.assertEquals(68, table.getCurrentSize()); table.setMaxSize(256); Assertions.assertEquals(68, table.getCurrentSize()); table.setMaxSize(67); Assertions.assertEquals(34, table.getCurrentSize()); table.setMaxSize(10); Assertions.assertEquals(0, table.getCurrentSize()); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/0040755 0000000 0000000 00000000000 00000250600 022122 5ustar000000000 0000000 httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestDefaultH2RequestConverter.java0100644 0000000 0000000 00000047020 00000250600 030644 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl; import java.util.Arrays; import java.util.List; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.net.URIAuthority; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestDefaultH2RequestConverter { @Test void testConvertFromFieldsBasic() throws Exception { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader(":authority", "www.example.com"), new BasicHeader(":path", "/"), new BasicHeader("custom123", "value")); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); final HttpRequest request = converter.convert(headers); Assertions.assertNotNull(request); Assertions.assertEquals("GET", request.getMethod()); Assertions.assertEquals("http", request.getScheme()); Assertions.assertEquals(new URIAuthority("www.example.com"), request.getAuthority()); Assertions.assertEquals("/", request.getPath()); final Header[] allHeaders = request.getHeaders(); Assertions.assertEquals(1, allHeaders.length); Assertions.assertEquals("custom123", allHeaders[0].getName()); Assertions.assertEquals("value", allHeaders[0].getValue()); } @Test void testConvertFromFieldsUpperCaseHeaderName() { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader(":authority", "www.example.com"), new BasicHeader(":Path", "/"), new BasicHeader("custom", "value")); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(headers), "Header name ':Path' is invalid (header name contains uppercase characters)"); } @Test void testConvertFromFieldsPseudoHeaderSequence() { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader("custom", "value"), new BasicHeader(":authority", "www.example.com"), new BasicHeader(":path", "/")); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(headers), "Invalid sequence of headers (pseudo-headers must precede message headers)"); } @Test void testConvertFromFieldsMissingMethod() { final List
headers = Arrays.asList( new BasicHeader(":scheme", "http"), new BasicHeader(":authority", "www.example.com"), new BasicHeader(":path", "/"), new BasicHeader("custom", "value")); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(headers), "Mandatory request header ':method' not found"); } @Test void testConvertFromFieldsMissingScheme() { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":authority", "www.example.com"), new BasicHeader(":path", "/"), new BasicHeader("custom", "value")); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(headers), "Mandatory request header ':scheme' not found"); } @Test void testConvertFromFieldsMissingPath() { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader(":authority", "www.example.com"), new BasicHeader("custom", "value")); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(headers), "Mandatory request header ':path' not found"); } @Test void testConvertFromFieldsUnknownPseudoHeader() { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader(":authority", "www.example.com"), new BasicHeader(":path", "/"), new BasicHeader(":custom", "value")); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(headers), "Unsupported request header ':custom'"); } @Test void testConvertFromFieldsMultipleMethod() { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader(":authority", "www.example.com"), new BasicHeader(":path", "/"), new BasicHeader("custom", "value")); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(headers), "Multiple ':method' request headers are illegal"); } @Test void testConvertFromFieldsMultipleScheme() { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader(":scheme", "https"), new BasicHeader(":authority", "www.example.com"), new BasicHeader(":path", "/"), new BasicHeader("custom", "value")); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(headers), "Multiple ':scheme' request headers are illegal"); } @Test void testConvertFromFieldsMultiplePath() { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "https"), new BasicHeader(":authority", "www.example.com"), new BasicHeader(":path", "/"), new BasicHeader(":path", "/"), new BasicHeader("custom", "value")); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(headers), "Multiple ':path' request headers are illegal"); } @Test void testConvertFromFieldsConnect() throws Exception { final List
headers = Arrays.asList( new BasicHeader(":method", "CONNECT"), new BasicHeader(":authority", "www.example.com"), new BasicHeader("custom", "value")); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); converter.convert(headers); } @Test void testConvertFromFieldsConnectMissingAuthority() { final List
headers = Arrays.asList( new BasicHeader(":method", "CONNECT"), new BasicHeader("custom", "value")); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(headers), "Header ':authority' is mandatory for CONNECT request"); } @Test void testConvertFromFieldsConnectPresentScheme() { final List
headers = Arrays.asList( new BasicHeader(":method", "CONNECT"), new BasicHeader(":scheme", "http"), new BasicHeader(":authority", "www.example.com"), new BasicHeader("custom", "value")); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(headers), "Header ':scheme' must not be set for CONNECT request"); } @Test void testConvertFromFieldsConnectPresentPath() { final List
headers = Arrays.asList( new BasicHeader(":method", "CONNECT"), new BasicHeader(":authority", "www.example.com"), new BasicHeader(":path", "/"), new BasicHeader("custom", "value")); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(headers), "Header ':path' must not be set for CONNECT request"); } @Test void testConvertFromMessageBasic() throws Exception { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("custom123", "Value"); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); final List
headers = converter.convert(request); Assertions.assertNotNull(headers); Assertions.assertEquals(5, headers.size()); final Header header1 = headers.get(0); Assertions.assertEquals(":method", header1.getName()); Assertions.assertEquals("GET", header1.getValue()); final Header header2 = headers.get(1); Assertions.assertEquals(":scheme", header2.getName()); Assertions.assertEquals("http", header2.getValue()); final Header header3 = headers.get(2); Assertions.assertEquals(":authority", header3.getName()); Assertions.assertEquals("host", header3.getValue()); final Header header4 = headers.get(3); Assertions.assertEquals(":path", header4.getName()); Assertions.assertEquals("/", header4.getValue()); final Header header5 = headers.get(4); Assertions.assertEquals("custom123", header5.getName()); Assertions.assertEquals("Value", header5.getValue()); } @Test void testConvertFromMessageMissingScheme() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("Custom123", "Value"); request.setScheme(null); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(request), "Request scheme is not set"); } @Test void testConvertFromMessageMissingPath() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("Custom123", "Value"); request.setPath(null); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(request), "Request path is not set"); } @Test void testConvertFromMessageConnect() throws Exception { final HttpRequest request = new BasicHttpRequest("CONNECT", new HttpHost("host:80"), null); request.addHeader("custom123", "Value"); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); final List
headers = converter.convert(request); Assertions.assertNotNull(headers); Assertions.assertEquals(3, headers.size()); final Header header1 = headers.get(0); Assertions.assertEquals(":method", header1.getName()); Assertions.assertEquals("CONNECT", header1.getValue()); final Header header2 = headers.get(1); Assertions.assertEquals(":authority", header2.getName()); Assertions.assertEquals("host:80", header2.getValue()); final Header header3 = headers.get(2); Assertions.assertEquals("custom123", header3.getName()); Assertions.assertEquals("Value", header3.getValue()); } @Test void testConvertFromMessageConnectMissingAuthority() { final HttpRequest request = new BasicHttpRequest("CONNECT", null, null); request.addHeader("Custom123", "Value"); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(request), "CONNECT request authority is not set"); } @Test void testConvertFromMessageConnectWithPath() { final HttpRequest request = new BasicHttpRequest("CONNECT", "/"); request.setAuthority(new URIAuthority("host")); request.addHeader("Custom123", "Value"); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(request), "CONNECT request path must be null"); } @Test void testConvertFromFieldsTETrailerHeader() throws Exception { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader(":authority", "www.example.com"), new BasicHeader(":path", "/"), new BasicHeader("te", "trailers")); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); final HttpRequest request = converter.convert(headers); Assertions.assertNotNull(request); Assertions.assertEquals("GET", request.getMethod()); Assertions.assertEquals("http", request.getScheme()); Assertions.assertEquals(new URIAuthority("www.example.com"), request.getAuthority()); Assertions.assertEquals("/", request.getPath()); final Header[] allHeaders = request.getHeaders(); Assertions.assertEquals(1, allHeaders.length); Assertions.assertEquals("te", allHeaders[0].getName()); Assertions.assertEquals("trailers", allHeaders[0].getValue()); } @Test void testConvertFromMessageInvalidHeader() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader(":custom", "stuff"); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(request), "Header name ':custom' is invalid"); } @Test void testValidPath() throws Exception { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader(":authority", "www.example.com"), new BasicHeader(":path", "/"), new BasicHeader("te", "trailers") ); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); final HttpRequest request = converter.convert(headers); Assertions.assertNotNull(request); Assertions.assertEquals("/", request.getPath()); } @Test void testInvalidPathEmpty() { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader(":authority", "www.example.com"), new BasicHeader(":path", ""), new BasicHeader("te", "trailers") ); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(ProtocolException.class, () -> converter.convert(headers)); } @Test void testInvalidPathNoSlash() { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader(":authority", "www.example.com"), new BasicHeader(":path", "noSlash"), new BasicHeader("te", "trailers") ); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(ProtocolException.class, () -> converter.convert(headers)); } @Test void testValidOptionsAsterisk() throws Exception { final List
headers = Arrays.asList( new BasicHeader(":method", "OPTIONS"), new BasicHeader(":scheme", "http"), new BasicHeader(":authority", "www.example.com"), new BasicHeader(":path", "*"), new BasicHeader("te", "trailers") ); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); final HttpRequest request = converter.convert(headers); Assertions.assertNotNull(request); Assertions.assertEquals("*", request.getPath()); } @Test void testValidOptionsWithRootPath() throws HttpException { final List
headers = Arrays.asList( new BasicHeader(":method", "OPTIONS"), new BasicHeader(":scheme", "http"), new BasicHeader(":authority", "www.example.com"), new BasicHeader(":path", "/"), new BasicHeader("te", "trailers") ); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); final HttpRequest request = converter.convert(headers); Assertions.assertNotNull(request); Assertions.assertEquals("/", request.getPath()); } @Test void testInvalidOptionsNeitherAsteriskNorRoot() { final List
headers = Arrays.asList( new BasicHeader(":method", "OPTIONS"), new BasicHeader(":scheme", "http"), new BasicHeader(":authority", "www.example.com"), new BasicHeader(":path", "invalid"), new BasicHeader("te", "trailers") ); final DefaultH2RequestConverter converter = new DefaultH2RequestConverter(); Assertions.assertThrows(ProtocolException.class, () -> converter.convert(headers)); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestDefaultH2ResponseConverter.java0100644 0000000 0000000 00000017504 00000250600 031016 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl; import java.util.Arrays; import java.util.List; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestDefaultH2ResponseConverter { @Test void testConvertFromFieldsBasic() throws Exception { final List
headers = Arrays.asList( new BasicHeader(":status", "200"), new BasicHeader("location", "http://www.example.com/"), new BasicHeader("custom123", "value")); final DefaultH2ResponseConverter converter = new DefaultH2ResponseConverter(); final HttpResponse response = converter.convert(headers); Assertions.assertNotNull(response ); Assertions.assertEquals(200, response .getCode()); final Header[] allHeaders = response.getHeaders(); Assertions.assertEquals(2, allHeaders.length); Assertions.assertEquals("location", allHeaders[0].getName()); Assertions.assertEquals("http://www.example.com/", allHeaders[0].getValue()); Assertions.assertEquals("custom123", allHeaders[1].getName()); Assertions.assertEquals("value", allHeaders[1].getValue()); } @Test void testConvertFromFieldsUpperCaseHeaderName() { final List
headers = Arrays.asList( new BasicHeader(":Status", "200"), new BasicHeader("location", "http://www.example.com/"), new BasicHeader("custom123", "value")); final DefaultH2ResponseConverter converter = new DefaultH2ResponseConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(headers), "Header name ':Status' is invalid (header name contains uppercase characters)"); } @Test void testConvertFromFieldsInvalidStatusCode() { final List
headers = Arrays.asList( new BasicHeader(":status", "boom"), new BasicHeader("location", "http://www.example.com/"), new BasicHeader("custom123", "value")); final DefaultH2ResponseConverter converter = new DefaultH2ResponseConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(headers)); } @Test void testConvertFromFieldsMissingStatus() { final List
headers = Arrays.asList( new BasicHeader("location", "http://www.example.com/"), new BasicHeader("custom", "value")); final DefaultH2ResponseConverter converter = new DefaultH2ResponseConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(headers), "Mandatory response header ':status' not found"); } @Test void testConvertFromFieldsUnknownPseudoHeader() { final List
headers = Arrays.asList( new BasicHeader(":status", "200"), new BasicHeader(":custom", "200"), new BasicHeader("location", "http://www.example.com/"), new BasicHeader("custom1", "value")); final DefaultH2ResponseConverter converter = new DefaultH2ResponseConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(headers), "Unsupported response header ':custom'"); } @Test void testConvertFromFieldsMultipleStatus() { final List
headers = Arrays.asList( new BasicHeader(":status", "200"), new BasicHeader(":status", "200"), new BasicHeader("location", "http://www.example.com/"), new BasicHeader("custom1", "value")); final DefaultH2ResponseConverter converter = new DefaultH2ResponseConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(headers), "Multiple ':status' response headers are illegal"); } @Test void testConvertFromMessageBasic() throws Exception { final HttpResponse response = new BasicHttpResponse(200); response.addHeader("custom123", "Value"); final DefaultH2ResponseConverter converter = new DefaultH2ResponseConverter(); final List
headers = converter.convert(response); Assertions.assertNotNull(headers); Assertions.assertEquals(2, headers.size()); final Header header1 = headers.get(0); Assertions.assertEquals(":status", header1.getName()); Assertions.assertEquals("200", header1.getValue()); final Header header2 = headers.get(1); Assertions.assertEquals("custom123", header2.getName()); Assertions.assertEquals("Value", header2.getValue()); } @Test void testConvertFromMessageInvalidStatus() { final HttpResponse response = new BasicHttpResponse(99); response.addHeader("Custom123", "Value"); final DefaultH2ResponseConverter converter = new DefaultH2ResponseConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(response), "Response status 99 is invalid"); } @Test void testConvertFromMessageInvalidHeader() { final HttpResponse response = new BasicHttpResponse(200); response.addHeader(":custom", "stuff"); final DefaultH2ResponseConverter converter = new DefaultH2ResponseConverter(); Assertions.assertThrows(HttpException.class, () -> converter.convert(response), "Header name ':custom' is invalid"); } @Test void testConvertFromFieldsMultipleCookies() throws Exception { final List
headers = Arrays.asList( new BasicHeader(":status", "200"), new BasicHeader("cookie", "a=b"), new BasicHeader("location", "http://www.example.com/"), new BasicHeader("cookie", "c=d"), new BasicHeader("cookie", "e=f")); final DefaultH2ResponseConverter converter = new DefaultH2ResponseConverter(); final HttpResponse response = converter.convert(headers); Assertions.assertNotNull(response ); Assertions.assertEquals(200, response .getCode()); final Header[] allHeaders = response.getHeaders(); Assertions.assertEquals(2, allHeaders.length); Assertions.assertEquals("location", allHeaders[0].getName()); Assertions.assertEquals("http://www.example.com/", allHeaders[0].getValue()); Assertions.assertEquals("cookie", allHeaders[1].getName()); Assertions.assertEquals("a=b; c=d; e=f", allHeaders[1].getValue()); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestFieldValidationSupport.java0100644 0000000 0000000 00000011371 00000250600 030260 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestFieldValidationSupport { @Test void testNameCharValid() throws Exception { Assertions.assertFalse(FieldValidationSupport.isNameCharValid(' ')); Assertions.assertTrue(FieldValidationSupport.isNameCharValid('a')); Assertions.assertTrue(FieldValidationSupport.isNameCharValid('A')); Assertions.assertTrue(FieldValidationSupport.isNameCharValid('0')); Assertions.assertTrue(FieldValidationSupport.isNameCharValid('@')); Assertions.assertFalse(FieldValidationSupport.isNameCharValid(':')); Assertions.assertFalse(FieldValidationSupport.isNameCharValid('ä')); } @Test void testNameCharLowerCaseValid() throws Exception { Assertions.assertFalse(FieldValidationSupport.isNameCharLowerCaseValid(' ')); Assertions.assertTrue(FieldValidationSupport.isNameCharLowerCaseValid('a')); Assertions.assertFalse(FieldValidationSupport.isNameCharLowerCaseValid('A')); Assertions.assertTrue(FieldValidationSupport.isNameCharLowerCaseValid('0')); Assertions.assertTrue(FieldValidationSupport.isNameCharLowerCaseValid('@')); Assertions.assertFalse(FieldValidationSupport.isNameCharLowerCaseValid(':')); Assertions.assertFalse(FieldValidationSupport.isNameCharLowerCaseValid('ä')); } @Test void testNameValid() throws Exception { Assertions.assertTrue(FieldValidationSupport.isNameValid("ABCDEF0123456789")); Assertions.assertFalse(FieldValidationSupport.isNameValid(":Blah")); Assertions.assertFalse(FieldValidationSupport.isNameValid("Blah ")); Assertions.assertFalse(FieldValidationSupport.isNameValid("Bläh")); } @Test void testNameLowerCaseValid() throws Exception { Assertions.assertTrue(FieldValidationSupport.isNameValid("abcdef0123456789")); Assertions.assertFalse(FieldValidationSupport.isNameValid(":blah")); Assertions.assertFalse(FieldValidationSupport.isNameValid("blah ")); Assertions.assertFalse(FieldValidationSupport.isNameValid("bläh")); } @Test void testValueCharValid() throws Exception { Assertions.assertTrue(FieldValidationSupport.isValueCharValid(' ')); Assertions.assertTrue(FieldValidationSupport.isValueCharValid('a')); Assertions.assertTrue(FieldValidationSupport.isValueCharValid('A')); Assertions.assertTrue(FieldValidationSupport.isValueCharValid('0')); Assertions.assertTrue(FieldValidationSupport.isValueCharValid('@')); Assertions.assertTrue(FieldValidationSupport.isValueCharValid(':')); Assertions.assertTrue(FieldValidationSupport.isValueCharValid('ä')); Assertions.assertFalse(FieldValidationSupport.isValueCharValid((char) 0x00)); Assertions.assertFalse(FieldValidationSupport.isValueCharValid('\n')); Assertions.assertFalse(FieldValidationSupport.isValueCharValid('\r')); } @Test void testValueValid() throws Exception { Assertions.assertTrue(FieldValidationSupport.isValueValid("ABCDEF0123456789")); Assertions.assertTrue(FieldValidationSupport.isValueValid(":Blah")); Assertions.assertTrue(FieldValidationSupport.isValueValid("Bläh")); Assertions.assertFalse(FieldValidationSupport.isValueValid(" Blah")); Assertions.assertFalse(FieldValidationSupport.isValueValid("Blah\t")); Assertions.assertFalse(FieldValidationSupport.isValueValid("Blah\nBlah")); Assertions.assertFalse(FieldValidationSupport.isValueValid("\rBlah")); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/io/0040755 0000000 0000000 00000000000 00000250600 022531 5ustar000000000 0000000 httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/io/MultiByteArrayInputStream.java0100644 0000000 0000000 00000006615 00000250600 030512 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.io; import java.io.IOException; import java.io.InputStream; import java.util.ArrayDeque; import java.util.Queue; class MultiByteArrayInputStream extends InputStream { private final Queue bufs; private byte[] current; private int pos; public MultiByteArrayInputStream(final byte[]... bufs) { super(); this.bufs = new ArrayDeque<>(); for (final byte[] buf: bufs) { if (buf.length > 0) { this.bufs.add(buf); } } } private void advance() { if (this.current != null) { if (this.pos >= this.current.length) { this.current = null; } } if (this.current == null) { this.current = this.bufs.poll(); this.pos = 0; } } @Override public int read() throws IOException { advance(); if (this.current == null) { return -1; } return this.current[this.pos++]; } @Override public int read(final byte b[], final int off, final int len) throws IOException { if (b == null) { throw new NullPointerException(); } else if (off < 0 || off > b.length || len < 0 || (off + len) > b.length || (off + len) < 0) { throw new IndexOutOfBoundsException(); } if (len == 0) { return 0; } advance(); if (this.current == null) { return -1; } final int chunk = Math.min(this.current.length - this.pos, len); if (chunk <= 0) { return 0; } System.arraycopy(this.current, this.pos, b, off, chunk); this.pos += chunk; return chunk; } @Override public long skip(final long n) { advance(); final int chunk = Math.min(this.current.length - this.pos, (int) n); if (chunk <= 0) { return 0; } this.pos += chunk; return chunk; } @Override public int available() { advance(); return this.current != null ? this.current.length - this.pos : 0; } @Override public boolean markSupported() { return false; } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/io/TestFrameInOutBuffers.java0100644 0000000 0000000 00000026375 00000250600 027574 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.io; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http2.H2ConnectionException; import org.apache.hc.core5.http2.H2CorruptFrameException; import org.apache.hc.core5.http2.frame.FrameConsts; import org.apache.hc.core5.http2.frame.FrameFlag; import org.apache.hc.core5.http2.frame.FrameType; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.impl.BasicH2TransportMetrics; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestFrameInOutBuffers { @Test void testReadWriteFrame() throws Exception { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final FrameOutputBuffer outbuffer = new FrameOutputBuffer(16 * 1024); final RawFrame frame = new RawFrame(FrameType.DATA.getValue(), 0, 1, ByteBuffer.wrap(new byte[]{1,2,3,4,5})); outbuffer.write(frame, outputStream); final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final byte[] bytes = outputStream.toByteArray(); final ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); Assertions.assertEquals(FrameConsts.HEAD_LEN + 5, bytes.length); Assertions.assertEquals(1, outbuffer.getMetrics().getFramesTransferred()); Assertions.assertEquals(bytes.length, outbuffer.getMetrics().getBytesTransferred()); final RawFrame frame2 = inBuffer.read(inputStream); Assertions.assertEquals(FrameType.DATA.getValue(), frame2.getType()); Assertions.assertEquals(0, frame2.getFlags()); Assertions.assertEquals(1L, frame2.getStreamId()); final ByteBuffer payload2 = frame2.getPayloadContent(); Assertions.assertNotNull(payload2); Assertions.assertEquals(5, payload2.remaining()); Assertions.assertEquals(1, payload2.get()); Assertions.assertEquals(2, payload2.get()); Assertions.assertEquals(3, payload2.get()); Assertions.assertEquals(4, payload2.get()); Assertions.assertEquals(5, payload2.get()); Assertions.assertEquals(-1, inputStream.read()); Assertions.assertEquals(1, inBuffer.getMetrics().getFramesTransferred()); Assertions.assertEquals(bytes.length, inBuffer.getMetrics().getBytesTransferred()); } @Test void testReadFrameMultiple() throws Exception { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final ByteArrayInputStream inputStream = new ByteArrayInputStream( new byte[] { 0,0,10,0,8,0,0,0,8,4,0,1,2,3,4,0,0,0,0, 0,0,10,0,9,0,0,0,8,4,5,6,7,8,9,0,0,0,0 }); final RawFrame frame1 = inBuffer.read(inputStream); Assertions.assertEquals(FrameType.DATA, FrameType.valueOf(frame1.getType())); Assertions.assertEquals(8, frame1.getFlags()); Assertions.assertEquals(8, frame1.getStreamId()); final ByteBuffer payload1 = frame1.getPayloadContent(); Assertions.assertNotNull(payload1); Assertions.assertEquals(5, payload1.remaining()); Assertions.assertEquals(0, payload1.get()); Assertions.assertEquals(1, payload1.get()); Assertions.assertEquals(2, payload1.get()); Assertions.assertEquals(3, payload1.get()); Assertions.assertEquals(4, payload1.get()); final RawFrame frame2 = inBuffer.read(inputStream); Assertions.assertEquals(FrameType.DATA, FrameType.valueOf(frame2.getType())); Assertions.assertEquals(FrameFlag.of(FrameFlag.END_STREAM, FrameFlag.PADDED), frame2.getFlags()); Assertions.assertEquals(8, frame2.getStreamId()); final ByteBuffer payload2 = frame2.getPayloadContent(); Assertions.assertNotNull(payload2); Assertions.assertEquals(5, payload2.remaining()); Assertions.assertEquals(5, payload2.get()); Assertions.assertEquals(6, payload2.get()); Assertions.assertEquals(7, payload2.get()); Assertions.assertEquals(8, payload2.get()); Assertions.assertEquals(9, payload2.get()); Assertions.assertEquals(-1, inputStream.read()); } @Test void testReadFrameMultipleSmallBuffer() throws Exception { final FrameInputBuffer inBuffer = new FrameInputBuffer(new BasicH2TransportMetrics(), 20, 10); final ByteArrayInputStream inputStream = new ByteArrayInputStream( new byte[] { 0,0,10,0,8,0,0,0,8,4,1,1,1,1,1,0,0,0,0, 0,0,5,0,0,0,0,0,8,2,2,2,2,2, 0,0,10,0,9,0,0,0,8,4,3,3,3,3,3,0,0,0,0 }); final RawFrame frame1 = inBuffer.read(inputStream); Assertions.assertEquals(FrameType.DATA, FrameType.valueOf(frame1.getType())); Assertions.assertEquals(8, frame1.getFlags()); Assertions.assertEquals(8, frame1.getStreamId()); final ByteBuffer payload1 = frame1.getPayloadContent(); Assertions.assertNotNull(payload1); Assertions.assertEquals(5, payload1.remaining()); Assertions.assertEquals(1, payload1.get()); Assertions.assertEquals(1, payload1.get()); Assertions.assertEquals(1, payload1.get()); Assertions.assertEquals(1, payload1.get()); Assertions.assertEquals(1, payload1.get()); final RawFrame frame2 = inBuffer.read(inputStream); Assertions.assertEquals(FrameType.DATA, FrameType.valueOf(frame2.getType())); Assertions.assertEquals(0, frame2.getFlags()); Assertions.assertEquals(8, frame2.getStreamId()); final ByteBuffer payload2 = frame2.getPayloadContent(); Assertions.assertNotNull(payload2); Assertions.assertEquals(5, payload2.remaining()); Assertions.assertEquals(2, payload2.get()); Assertions.assertEquals(2, payload2.get()); Assertions.assertEquals(2, payload2.get()); Assertions.assertEquals(2, payload2.get()); Assertions.assertEquals(2, payload2.get()); final RawFrame frame3 = inBuffer.read(inputStream); Assertions.assertEquals(FrameType.DATA, FrameType.valueOf(frame3.getType())); Assertions.assertEquals(FrameFlag.of(FrameFlag.END_STREAM, FrameFlag.PADDED), frame3.getFlags()); Assertions.assertEquals(8, frame3.getStreamId()); final ByteBuffer payload3 = frame3.getPayloadContent(); Assertions.assertNotNull(payload3); Assertions.assertEquals(5, payload3.remaining()); Assertions.assertEquals(3, payload3.get()); Assertions.assertEquals(3, payload3.get()); Assertions.assertEquals(3, payload3.get()); Assertions.assertEquals(3, payload3.get()); Assertions.assertEquals(3, payload3.get()); Assertions.assertEquals(-1, inputStream.read()); } @Test void testReadFramePartialReads() throws Exception { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final MultiByteArrayInputStream inputStream = new MultiByteArrayInputStream( new byte[] {0,0}, new byte[] {10,0,9,0}, new byte[] {0,0,8}, new byte[] {4}, new byte[] {1,2,3,4}, new byte[] {5,0}, new byte[] {0,0,0}); final RawFrame frame = inBuffer.read(inputStream); Assertions.assertEquals(FrameType.DATA, FrameType.valueOf(frame.getType())); Assertions.assertEquals(FrameFlag.of(FrameFlag.END_STREAM, FrameFlag.PADDED), frame.getFlags()); Assertions.assertEquals(8, frame.getStreamId()); final ByteBuffer payload = frame.getPayloadContent(); Assertions.assertNotNull(payload); Assertions.assertEquals(5, payload.remaining()); Assertions.assertEquals(1, payload.get()); Assertions.assertEquals(2, payload.get()); Assertions.assertEquals(3, payload.get()); Assertions.assertEquals(4, payload.get()); Assertions.assertEquals(5, payload.get()); Assertions.assertEquals(-1, inputStream.read()); } @Test void testReadEmptyFrame() throws Exception { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[] {0,0,0,0,0,0,0,0,0}); final RawFrame frame = inBuffer.read(inputStream); Assertions.assertEquals(FrameType.DATA, FrameType.valueOf(frame.getType())); Assertions.assertEquals(0, frame.getFlags()); Assertions.assertEquals(0, frame.getStreamId()); final ByteBuffer payload = frame.getPayloadContent(); Assertions.assertNull(payload); } @Test void testReadFrameConnectionClosed() { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[] {}); Assertions.assertThrows(ConnectionClosedException.class, () -> inBuffer.read(inputStream)); } @Test void testReadFrameCorruptFrame() { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[] {0,0}); Assertions.assertThrows(H2CorruptFrameException.class, () -> inBuffer.read(inputStream)); } @Test void testWriteFrameExceedingLimit() { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final FrameOutputBuffer outbuffer = new FrameOutputBuffer(1024); final RawFrame frame = new RawFrame(FrameType.DATA.getValue(), 0, 1, ByteBuffer.wrap(new byte[2048])); Assertions.assertThrows(H2ConnectionException.class, () -> outbuffer.write(frame, outputStream)); } @Test void testReadFrameExceedingLimit() { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final ByteArrayInputStream inputStream = new ByteArrayInputStream( new byte[] {0,-128,-128,0,0,0,0,0,1}); Assertions.assertThrows(H2ConnectionException.class, () -> inBuffer.read(inputStream)); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/0040755 0000000 0000000 00000000000 00000250600 022707 5ustar000000000 0000000 httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestAbstractH2StreamMultiplexer.java0100644 0000000 0000000 00000145331 00000250600 031762 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.io.IOException; import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.stream.IntStream; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.impl.CharCodingSupport; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.AsyncPushProducer; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http2.H2ConnectionException; import org.apache.hc.core5.http2.H2Error; import org.apache.hc.core5.http2.H2StreamResetException; import org.apache.hc.core5.http2.WritableByteChannelMock; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.config.H2Param; import org.apache.hc.core5.http2.config.H2Setting; import org.apache.hc.core5.http2.frame.DefaultFrameFactory; import org.apache.hc.core5.http2.frame.FrameConsts; import org.apache.hc.core5.http2.frame.FrameFactory; import org.apache.hc.core5.http2.frame.FrameType; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.frame.StreamIdGenerator; import org.apache.hc.core5.http2.hpack.HPackDecoder; import org.apache.hc.core5.http2.hpack.HPackEncoder; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.util.ByteArrayBuffer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; class TestAbstractH2StreamMultiplexer { private static final FrameFactory FRAME_FACTORY = DefaultFrameFactory.INSTANCE; @Mock ProtocolIOSession protocolIOSession; @Mock Lock lock; @Mock HttpProcessor httpProcessor; @Mock H2StreamListener h2StreamListener; @Mock H2StreamHandler streamHandler; @Captor ArgumentCaptor> headersCaptor; @Captor ArgumentCaptor exceptionCaptor; @BeforeEach void prepareMocks() { MockitoAnnotations.openMocks(this); Mockito.when(protocolIOSession.getLock()).thenReturn(lock); } static class H2StreamMultiplexerImpl extends AbstractH2StreamMultiplexer { private Supplier streamHandlerSupplier; public H2StreamMultiplexerImpl( final ProtocolIOSession ioSession, final FrameFactory frameFactory, final StreamIdGenerator idGenerator, final HttpProcessor httpProcessor, final CharCodingConfig charCodingConfig, final H2Config h2Config, final H2StreamListener streamListener, final Supplier streamHandlerSupplier) { super(ioSession, frameFactory, idGenerator, httpProcessor, charCodingConfig, h2Config, streamListener); this.streamHandlerSupplier = streamHandlerSupplier; } @Override void validateSetting(final H2Param param, final int value) throws H2ConnectionException { } @Override H2Setting[] generateSettings(final H2Config localConfig) { return new H2Setting[] { new H2Setting(H2Param.HEADER_TABLE_SIZE, localConfig.getHeaderTableSize()), new H2Setting(H2Param.ENABLE_PUSH, localConfig.isPushEnabled() ? 1 : 0), new H2Setting(H2Param.MAX_CONCURRENT_STREAMS, localConfig.getMaxConcurrentStreams()), new H2Setting(H2Param.INITIAL_WINDOW_SIZE, localConfig.getInitialWindowSize()), new H2Setting(H2Param.MAX_FRAME_SIZE, localConfig.getMaxFrameSize()), new H2Setting(H2Param.MAX_HEADER_LIST_SIZE, localConfig.getMaxHeaderListSize()) }; } @Override void acceptHeaderFrame() throws H2ConnectionException { } @Override void acceptPushRequest() throws H2ConnectionException { } @Override void acceptPushFrame() throws H2ConnectionException { } @Override H2StreamHandler incomingRequest(final H2StreamChannel channel) { return streamHandlerSupplier.get(); } @Override H2StreamHandler outgoingRequest(final H2StreamChannel channel, final AsyncClientExchangeHandler exchangeHandler, final HandlerFactory pushHandlerFactory, final HttpContext context) { return null; } @Override H2StreamHandler incomingPushPromise(final H2StreamChannel channel, final HandlerFactory pushHandlerFactory) { return streamHandlerSupplier.get(); } @Override H2StreamHandler outgoingPushPromise(final H2StreamChannel channel, final AsyncPushProducer pushProducer) { return null; } @Override boolean allowGracefulAbort(final H2Stream stream) { return stream.isRemoteClosed() && !stream.isLocalClosed(); } } @Test void testInputOneFrame() throws Exception { final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); final FrameOutputBuffer outbuffer = new FrameOutputBuffer(16 * 1024); final byte[] data = new byte[FrameConsts.MIN_FRAME_SIZE]; for (int i = 0; i < FrameConsts.MIN_FRAME_SIZE; i++) { data[i] = (byte)(i % 16); } final RawFrame frame = new RawFrame(FrameType.DATA.getValue(), 0, 1, ByteBuffer.wrap(data)); outbuffer.write(frame, writableChannel); final byte[] bytes = writableChannel.toByteArray(); final AbstractH2StreamMultiplexer streamMultiplexer = new H2StreamMultiplexerImpl( protocolIOSession, FRAME_FACTORY, StreamIdGenerator.ODD, httpProcessor, CharCodingConfig.DEFAULT, H2Config.custom() .setMaxFrameSize(FrameConsts.MIN_FRAME_SIZE) .build(), h2StreamListener, () -> streamHandler); Assertions.assertThrows(H2ConnectionException.class, () -> streamMultiplexer.onInput(ByteBuffer.wrap(bytes))); Mockito.verify(h2StreamListener).onFrameInput( ArgumentMatchers.same(streamMultiplexer), ArgumentMatchers.eq(1), ArgumentMatchers.any()); Assertions.assertThrows(H2ConnectionException.class, () -> { int pos = 0; int remaining = bytes.length; while (remaining > 0) { final int chunk = Math.min(2048, remaining); streamMultiplexer.onInput(ByteBuffer.wrap(bytes, pos, chunk)); pos += chunk; remaining -= chunk; } Mockito.verify(h2StreamListener).onFrameInput( ArgumentMatchers.same(streamMultiplexer), ArgumentMatchers.eq(1), ArgumentMatchers.any()); }); } @Test void testInputMultipleFrames() throws Exception { final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); final FrameOutputBuffer outbuffer = new FrameOutputBuffer(16 * 1024); final byte[] data = new byte[FrameConsts.MIN_FRAME_SIZE]; for (int i = 0; i < FrameConsts.MIN_FRAME_SIZE; i++) { data[i] = (byte)(i % 16); } final RawFrame frame1 = new RawFrame(FrameType.DATA.getValue(), 0, 1, ByteBuffer.wrap(data)); outbuffer.write(frame1, writableChannel); final RawFrame frame2 = new RawFrame(FrameType.DATA.getValue(), 0, 1, ByteBuffer.wrap(data)); outbuffer.write(frame2, writableChannel); final byte[] bytes = writableChannel.toByteArray(); final AbstractH2StreamMultiplexer streamMultiplexer = new H2StreamMultiplexerImpl( protocolIOSession, FRAME_FACTORY, StreamIdGenerator.ODD, httpProcessor, CharCodingConfig.DEFAULT, H2Config.custom() .setMaxFrameSize(FrameConsts.MIN_FRAME_SIZE) .build(), h2StreamListener, () -> streamHandler); Assertions.assertThrows(H2ConnectionException.class, () -> streamMultiplexer.onInput(ByteBuffer.wrap(bytes))); Mockito.verify(h2StreamListener).onFrameInput( ArgumentMatchers.same(streamMultiplexer), ArgumentMatchers.eq(1), ArgumentMatchers.any()); Assertions.assertThrows(H2ConnectionException.class, () -> { int pos = 0; int remaining = bytes.length; while (remaining > 0) { final int chunk = Math.min(4096, remaining); streamMultiplexer.onInput(ByteBuffer.wrap(bytes, pos, chunk)); pos += chunk; remaining -= chunk; } Mockito.verify(h2StreamListener).onFrameInput( ArgumentMatchers.same(streamMultiplexer), ArgumentMatchers.eq(1), ArgumentMatchers.any()); }); } @Test void testInputHeaderContinuationFrame() throws Exception { final H2Config h2Config = H2Config.custom().setMaxFrameSize(FrameConsts.MIN_FRAME_SIZE) .build(); final ByteArrayBuffer buf = new ByteArrayBuffer(19); final HPackEncoder encoder = new HPackEncoder(H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = new ArrayList<>(); headers.add(new BasicHeader("test-header-key", "value")); headers.add(new BasicHeader(":status", "200")); encoder.encodeHeaders(buf, headers, h2Config.isCompressionEnabled()); final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); final FrameOutputBuffer outBuffer = new FrameOutputBuffer(16 * 1024); final RawFrame headerFrame = FRAME_FACTORY.createHeaders(2, ByteBuffer.wrap(buf.array(), 0, 10), false, false); outBuffer.write(headerFrame, writableChannel); final RawFrame continuationFrame = FRAME_FACTORY.createContinuation(2, ByteBuffer.wrap(buf.array(), 10, 9), true); outBuffer.write(continuationFrame, writableChannel); final byte[] bytes = writableChannel.toByteArray(); final AbstractH2StreamMultiplexer streamMultiplexer = new H2StreamMultiplexerImpl( protocolIOSession, FRAME_FACTORY, StreamIdGenerator.ODD, httpProcessor, CharCodingConfig.DEFAULT, h2Config, h2StreamListener, () -> streamHandler); streamMultiplexer.onInput(ByteBuffer.wrap(bytes)); Mockito.verify(streamHandler).consumeHeader(headersCaptor.capture(), ArgumentMatchers.eq(false)); Assertions.assertFalse(headersCaptor.getValue().isEmpty()); } @Test void testZeroIncrement() throws Exception { final H2Config h2Config = H2Config.custom() .build(); final AbstractH2StreamMultiplexer streamMultiplexer = new H2StreamMultiplexerImpl( protocolIOSession, FRAME_FACTORY, StreamIdGenerator.EVEN, httpProcessor, CharCodingConfig.DEFAULT, h2Config, h2StreamListener, () -> streamHandler); final ByteArrayBuffer headerBuf = new ByteArrayBuffer(200); final HPackEncoder encoder = new HPackEncoder(h2Config.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader(":path", "/"), new BasicHeader(":authority", "www.example.com")); encoder.encodeHeaders(headerBuf, headers, h2Config.isCompressionEnabled()); final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); final FrameOutputBuffer outBuffer = new FrameOutputBuffer(16 * 1024); final RawFrame headerFrame = FRAME_FACTORY.createHeaders(1, ByteBuffer.wrap(headerBuf.array(), 0, headerBuf.length()), true, true); outBuffer.write(headerFrame, writableChannel); streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); Mockito.verify(streamHandler).consumeHeader(headersCaptor.capture(), ArgumentMatchers.eq(true)); Assertions.assertFalse(headersCaptor.getValue().isEmpty()); writableChannel.reset(); final ByteBuffer payload = ByteBuffer.allocate(4); payload.putInt(0); payload.flip(); final RawFrame incrementFrame = new RawFrame(FrameType.WINDOW_UPDATE.getValue(), 0, 1, payload); outBuffer.write(incrementFrame, writableChannel); final H2ConnectionException exception = Assertions.assertThrows(H2ConnectionException.class, () -> streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray()))); Assertions.assertEquals(H2Error.PROTOCOL_ERROR, H2Error.getByCode(exception.getCode())); } @Test void testIncrementOverflow() throws Exception { final H2Config h2Config = H2Config.custom() .build(); final AbstractH2StreamMultiplexer streamMultiplexer = new H2StreamMultiplexerImpl( protocolIOSession, FRAME_FACTORY, StreamIdGenerator.EVEN, httpProcessor, CharCodingConfig.DEFAULT, h2Config, h2StreamListener, () -> streamHandler); final ByteArrayBuffer headerBuf = new ByteArrayBuffer(200); final HPackEncoder encoder = new HPackEncoder(h2Config.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader(":path", "/"), new BasicHeader(":authority", "www.example.com")); encoder.encodeHeaders(headerBuf, headers, h2Config.isCompressionEnabled()); final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); final FrameOutputBuffer outBuffer = new FrameOutputBuffer(16 * 1024); final RawFrame headerFrame = FRAME_FACTORY.createHeaders(1, ByteBuffer.wrap(headerBuf.array(), 0, headerBuf.length()), true, true); outBuffer.write(headerFrame, writableChannel); streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); Mockito.verify(streamHandler).consumeHeader(headersCaptor.capture(), ArgumentMatchers.eq(true)); Assertions.assertFalse(headersCaptor.getValue().isEmpty()); writableChannel.reset(); final RawFrame incrementFrame1 = FRAME_FACTORY.createWindowUpdate(1, 100); outBuffer.write(incrementFrame1, writableChannel); streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); writableChannel.reset(); final RawFrame incrementFrame2 = FRAME_FACTORY.createWindowUpdate(1, 0x7fffffff - 50); outBuffer.write(incrementFrame2, writableChannel); final H2ConnectionException exception = Assertions.assertThrows(H2ConnectionException.class, () -> streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray()))); Assertions.assertEquals(H2Error.FLOW_CONTROL_ERROR, H2Error.getByCode(exception.getCode())); } @Test void testHeadersAfterEndOfStream() throws Exception { final H2Config h2Config = H2Config.custom() .build(); final AbstractH2StreamMultiplexer streamMultiplexer = new H2StreamMultiplexerImpl( protocolIOSession, FRAME_FACTORY, StreamIdGenerator.EVEN, httpProcessor, CharCodingConfig.DEFAULT, h2Config, h2StreamListener, () -> streamHandler); final ByteArrayBuffer headerBuf = new ByteArrayBuffer(200); final HPackEncoder encoder = new HPackEncoder(h2Config.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader(":path", "/"), new BasicHeader(":authority", "www.example.com")); encoder.encodeHeaders(headerBuf, headers, h2Config.isCompressionEnabled()); final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); final FrameOutputBuffer outBuffer = new FrameOutputBuffer(16 * 1024); final RawFrame headerFrame1 = FRAME_FACTORY.createHeaders(1, ByteBuffer.wrap(headerBuf.array(), 0, headerBuf.length()), true, true); outBuffer.write(headerFrame1, writableChannel); streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); Mockito.verify(streamHandler).consumeHeader(headersCaptor.capture(), ArgumentMatchers.eq(true)); Assertions.assertFalse(headersCaptor.getValue().isEmpty()); writableChannel.reset(); final RawFrame headerFrame2 = FRAME_FACTORY.createHeaders(1, ByteBuffer.wrap(headerBuf.array(), 0, headerBuf.length()), true, true); outBuffer.write(headerFrame2, writableChannel); // Treat the first occurrence as a stream error streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); Mockito.verify(streamHandler).failed(exceptionCaptor.capture()); Assertions.assertInstanceOf(H2StreamResetException.class, exceptionCaptor.getValue()); writableChannel.reset(); final RawFrame headerFrame3 = FRAME_FACTORY.createHeaders(1, ByteBuffer.wrap(headerBuf.array(), 0, headerBuf.length()), true, true); outBuffer.write(headerFrame3, writableChannel); // Treat subsequent occurrences as a connection-wide protocol error final H2ConnectionException exception = Assertions.assertThrows(H2ConnectionException.class, () -> streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray()))); Assertions.assertEquals(H2Error.STREAM_CLOSED, H2Error.getByCode(exception.getCode())); } @Test void testDataAfterEndOfStream() throws Exception { final H2Config h2Config = H2Config.custom() .build(); final AbstractH2StreamMultiplexer streamMultiplexer = new H2StreamMultiplexerImpl( protocolIOSession, FRAME_FACTORY, StreamIdGenerator.EVEN, httpProcessor, CharCodingConfig.DEFAULT, h2Config, h2StreamListener, () -> streamHandler); final ByteArrayBuffer headerBuf = new ByteArrayBuffer(200); final HPackEncoder encoder = new HPackEncoder(h2Config.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader(":path", "/"), new BasicHeader(":authority", "www.example.com")); encoder.encodeHeaders(headerBuf, headers, h2Config.isCompressionEnabled()); final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); final FrameOutputBuffer outBuffer = new FrameOutputBuffer(16 * 1024); final RawFrame headerFrame1 = FRAME_FACTORY.createHeaders(1, ByteBuffer.wrap(headerBuf.array(), 0, headerBuf.length()), true, true); outBuffer.write(headerFrame1, writableChannel); streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); Mockito.verify(streamHandler).consumeHeader(headersCaptor.capture(), ArgumentMatchers.eq(true)); Assertions.assertFalse(headersCaptor.getValue().isEmpty()); writableChannel.reset(); final RawFrame dataFrame1 = FRAME_FACTORY.createData(1, ByteBuffer.wrap(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}), true); outBuffer.write(dataFrame1, writableChannel); // Treat the first occurrence as a stream error streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); Mockito.verify(streamHandler).failed(exceptionCaptor.capture()); Assertions.assertInstanceOf(H2StreamResetException.class, exceptionCaptor.getValue()); writableChannel.reset(); final RawFrame dataFrame2 = FRAME_FACTORY.createData(1, ByteBuffer.wrap(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}), true); outBuffer.write(dataFrame2, writableChannel); // Treat subsequent occurrences as a connection-wide protocol error final H2ConnectionException exception = Assertions.assertThrows(H2ConnectionException.class, () -> streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray()))); Assertions.assertEquals(H2Error.STREAM_CLOSED, H2Error.getByCode(exception.getCode())); } @Test void testContinuationAfterEndOfStream() throws Exception { final H2Config h2Config = H2Config.custom() .build(); final AbstractH2StreamMultiplexer streamMultiplexer = new H2StreamMultiplexerImpl( protocolIOSession, FRAME_FACTORY, StreamIdGenerator.EVEN, httpProcessor, CharCodingConfig.DEFAULT, h2Config, h2StreamListener, () -> streamHandler); final ByteArrayBuffer headerBuf = new ByteArrayBuffer(200); final HPackEncoder encoder = new HPackEncoder(h2Config.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), new BasicHeader(":path", "/"), new BasicHeader(":authority", "www.example.com")); encoder.encodeHeaders(headerBuf, headers, h2Config.isCompressionEnabled()); final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); final FrameOutputBuffer outBuffer = new FrameOutputBuffer(16 * 1024); final RawFrame headerFrame1 = FRAME_FACTORY.createHeaders(1, ByteBuffer.wrap(headerBuf.array(), 0, headerBuf.length()), true, true); outBuffer.write(headerFrame1, writableChannel); streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); Mockito.verify(streamHandler).consumeHeader(headersCaptor.capture(), ArgumentMatchers.eq(true)); Assertions.assertFalse(headersCaptor.getValue().isEmpty()); writableChannel.reset(); final RawFrame continuationFrame = FRAME_FACTORY.createContinuation(1, ByteBuffer.wrap(headerBuf.array(), 0, headerBuf.length()), true); outBuffer.write(continuationFrame, writableChannel); final H2ConnectionException exception = Assertions.assertThrows(H2ConnectionException.class, () -> streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray()))); Assertions.assertEquals(H2Error.PROTOCOL_ERROR, H2Error.getByCode(exception.getCode())); } @Test void testInputHeaderContinuationFramesNoLimit() throws Exception { final H2Config h2Config = H2Config.custom() .setMaxContinuations(Integer.MAX_VALUE) .build(); final ByteArrayBuffer headerBuf = new ByteArrayBuffer(19); final HPackEncoder encoder = new HPackEncoder(H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = new ArrayList<>(); headers.add(new BasicHeader(":status", "200")); for (int i = 1; i <= 100; i++) { headers.add(new BasicHeader("test-header-key-" + i, "value-" + i)); } encoder.encodeHeaders(headerBuf, headers, h2Config.isCompressionEnabled()); Assertions.assertTrue(headerBuf.length() > 750); final AbstractH2StreamMultiplexer streamMultiplexer = new H2StreamMultiplexerImpl( protocolIOSession, FRAME_FACTORY, StreamIdGenerator.ODD, httpProcessor, CharCodingConfig.DEFAULT, h2Config, h2StreamListener, () -> streamHandler); final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); final FrameOutputBuffer outBuffer = new FrameOutputBuffer(16 * 1024); final RawFrame headerFrame = FRAME_FACTORY.createHeaders(2, ByteBuffer.wrap(headerBuf.array(), 0, 250), false, false); outBuffer.write(headerFrame, writableChannel); streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); writableChannel.reset(); final RawFrame continuationFrame1 = FRAME_FACTORY.createContinuation(2, ByteBuffer.wrap(headerBuf.array(), 250, 250), false); outBuffer.write(continuationFrame1, writableChannel); streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); writableChannel.reset(); final RawFrame continuationFrame2 = FRAME_FACTORY.createContinuation(2, ByteBuffer.wrap(headerBuf.array(), 500, 250), false); outBuffer.write(continuationFrame2, writableChannel); streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); writableChannel.reset(); final RawFrame continuationFrame3 = FRAME_FACTORY.createContinuation(2, ByteBuffer.wrap(headerBuf.array(), 750, headerBuf.length() - 750), true); outBuffer.write(continuationFrame3, writableChannel); streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); Mockito.verify(streamHandler).consumeHeader(headersCaptor.capture(), ArgumentMatchers.eq(false)); Assertions.assertFalse(headersCaptor.getValue().isEmpty()); } @Test void testInputHeaderContinuationFramesMaxLimit() throws Exception { final H2Config h2Config = H2Config.custom() .setMaxContinuations(2) .build(); final ByteArrayBuffer headerBuf = new ByteArrayBuffer(19); final HPackEncoder encoder = new HPackEncoder(H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = new ArrayList<>(); headers.add(new BasicHeader(":status", "200")); for (int i = 1; i <= 100; i++) { headers.add(new BasicHeader("test-header-key-" + i, "value-" + i)); } encoder.encodeHeaders(headerBuf, headers, h2Config.isCompressionEnabled()); Assertions.assertTrue(headerBuf.length() > 750); final AbstractH2StreamMultiplexer streamMultiplexer = new H2StreamMultiplexerImpl( protocolIOSession, FRAME_FACTORY, StreamIdGenerator.ODD, httpProcessor, CharCodingConfig.DEFAULT, h2Config, h2StreamListener, () -> streamHandler); final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); final FrameOutputBuffer outBuffer = new FrameOutputBuffer(16 * 1024); final RawFrame headerFrame = FRAME_FACTORY.createHeaders(2, ByteBuffer.wrap(headerBuf.array(), 0, 250), false, false); outBuffer.write(headerFrame, writableChannel); streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); writableChannel.reset(); final RawFrame continuationFrame1 = FRAME_FACTORY.createContinuation(2, ByteBuffer.wrap(headerBuf.array(), 250, 250), false); outBuffer.write(continuationFrame1, writableChannel); streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); writableChannel.reset(); final RawFrame continuationFrame2 = FRAME_FACTORY.createContinuation(2, ByteBuffer.wrap(headerBuf.array(), 500, 250), false); outBuffer.write(continuationFrame2, writableChannel); streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); writableChannel.reset(); final RawFrame continuationFrame3 = FRAME_FACTORY.createContinuation(2, ByteBuffer.wrap(headerBuf.array(), 750, headerBuf.length() - 750), true); outBuffer.write(continuationFrame3, writableChannel); Assertions.assertThrows(H2ConnectionException.class, () -> streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray()))); } @Test void testStreamRemoteReset() throws Exception { final H2Config h2Config = H2Config.custom() .build(); final AbstractH2StreamMultiplexer streamMultiplexer = new H2StreamMultiplexerImpl( protocolIOSession, FRAME_FACTORY, StreamIdGenerator.ODD, httpProcessor, CharCodingConfig.DEFAULT, h2Config, h2StreamListener, () -> streamHandler); final H2StreamChannel channel = streamMultiplexer.createChannel(1); final H2Stream stream = streamMultiplexer.createStream(channel, streamHandler); final ByteArrayBuffer buf = new ByteArrayBuffer(19); final HPackEncoder encoder = new HPackEncoder(H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = new ArrayList<>(); headers.add(new BasicHeader(":status", "200")); encoder.encodeHeaders(buf, headers, h2Config.isCompressionEnabled()); final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); final FrameOutputBuffer outBuffer = new FrameOutputBuffer(16 * 1024); final RawFrame headerFrame = FRAME_FACTORY.createHeaders(1, ByteBuffer.wrap(buf.array(), 0, 10), true, false); outBuffer.write(headerFrame, writableChannel); streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); Assertions.assertFalse(stream.isRemoteClosed()); Assertions.assertFalse(stream.isLocalClosed()); final RawFrame resetFrame = FRAME_FACTORY.createResetStream(1, H2Error.NO_ERROR); outBuffer.write(resetFrame, writableChannel); streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); Assertions.assertTrue(stream.isRemoteClosed()); Assertions.assertTrue(stream.isLocalClosed()); final ArgumentCaptor exceptionCaptor = ArgumentCaptor.forClass(Exception.class); Mockito.verify(streamHandler).failed(exceptionCaptor.capture()); Assertions.assertInstanceOf(H2StreamResetException.class, exceptionCaptor.getValue()); } @Test void testStreamRemoteResetNoErrorRemoteAlreadyClosed() throws Exception { final H2Config h2Config = H2Config.custom() .build(); final AbstractH2StreamMultiplexer streamMultiplexer = new H2StreamMultiplexerImpl( protocolIOSession, FRAME_FACTORY, StreamIdGenerator.ODD, httpProcessor, CharCodingConfig.DEFAULT, h2Config, h2StreamListener, () -> streamHandler); final H2StreamChannel channel = streamMultiplexer.createChannel(1); final H2Stream stream = streamMultiplexer.createStream(channel, streamHandler); final ByteArrayBuffer buf = new ByteArrayBuffer(19); final HPackEncoder encoder = new HPackEncoder(H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = new ArrayList<>(); headers.add(new BasicHeader(":status", "200")); encoder.encodeHeaders(buf, headers, h2Config.isCompressionEnabled()); final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); final FrameOutputBuffer outBuffer = new FrameOutputBuffer(16 * 1024); final RawFrame headerFrame = FRAME_FACTORY.createHeaders(1, ByteBuffer.wrap(buf.array(), 0, 10), true, false); outBuffer.write(headerFrame, writableChannel); streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); writableChannel.reset(); final RawFrame dataFrame = FRAME_FACTORY.createData(1, ByteBuffer.wrap(new byte[] { 'D', 'o', 'n', 'e'}), true); outBuffer.write(dataFrame, writableChannel); streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); Assertions.assertTrue(stream.isRemoteClosed()); Assertions.assertFalse(stream.isLocalClosed()); writableChannel.reset(); final RawFrame resetFrame = FRAME_FACTORY.createResetStream(1, H2Error.NO_ERROR); outBuffer.write(resetFrame, writableChannel); streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); Assertions.assertTrue(stream.isRemoteClosed()); Assertions.assertTrue(stream.isLocalClosed()); Mockito.verify(streamHandler, Mockito.never()).failed(ArgumentMatchers.any()); } @Test void testPriorityUpdateInputAccepted() throws Exception { final AbstractH2StreamMultiplexer mux = new H2StreamMultiplexerImpl( protocolIOSession, FRAME_FACTORY, StreamIdGenerator.ODD, httpProcessor, CharCodingConfig.DEFAULT, H2Config.custom().setMaxFrameSize(FrameConsts.MIN_FRAME_SIZE).build(), h2StreamListener, () -> streamHandler); // Make stream id=1 "seen" so lookupSeen(1) does not fail. final H2StreamChannel channel = mux.createChannel(1); mux.createStream(channel, streamHandler); final WritableByteChannelMock writable = new WritableByteChannelMock(1024); final FrameOutputBuffer fob = new FrameOutputBuffer(16 * 1024); final byte[] ascii = "u=3,i".getBytes(StandardCharsets.US_ASCII); final ByteBuffer payload = ByteBuffer.allocate(4 + ascii.length); payload.putInt(1); // prioritized stream id = 1 payload.put(ascii); payload.flip(); final RawFrame priUpd = new RawFrame(FrameType.PRIORITY_UPDATE.getValue(), 0, 0, payload); fob.write(priUpd, writable); final byte[] bytes = writable.toByteArray(); Assertions.assertDoesNotThrow(() -> mux.onInput(ByteBuffer.wrap(bytes))); Mockito.verify(h2StreamListener).onFrameInput( ArgumentMatchers.same(mux), ArgumentMatchers.eq(0), ArgumentMatchers.any()); } @Test void testPriorityUpdateInputRejectedForUnseenStream() throws Exception { final AbstractH2StreamMultiplexer mux = new H2StreamMultiplexerImpl( protocolIOSession, FRAME_FACTORY, StreamIdGenerator.ODD, httpProcessor, CharCodingConfig.DEFAULT, H2Config.custom().setMaxFrameSize(FrameConsts.MIN_FRAME_SIZE).build(), h2StreamListener, () -> streamHandler); final WritableByteChannelMock writable = new WritableByteChannelMock(1024); final FrameOutputBuffer fob = new FrameOutputBuffer(16 * 1024); final byte[] ascii = "u=3,i".getBytes(StandardCharsets.US_ASCII); final ByteBuffer payload = ByteBuffer.allocate(4 + ascii.length); payload.putInt(1); // prioritized stream id = 1 (unseen) payload.put(ascii); payload.flip(); final RawFrame priUpd = new RawFrame(FrameType.PRIORITY_UPDATE.getValue(), 0, 0, payload); fob.write(priUpd, writable); final byte[] bytes = writable.toByteArray(); final H2ConnectionException ex = Assertions.assertThrows( H2ConnectionException.class, () -> mux.onInput(ByteBuffer.wrap(bytes))); Assertions.assertEquals("Unexpected stream id: 1", ex.getMessage()); } // Helper: minimal stream handler that sends our headers once static final class PriorityHeaderSender implements H2StreamHandler { private final H2StreamChannel channel; private final List
headers; private final boolean endStream; private boolean sent; PriorityHeaderSender(final H2StreamChannel channel, final List
headers, final boolean endStream) { this.channel = channel; this.headers = headers; this.endStream = endStream; this.sent = false; } @Override public HandlerFactory getPushHandlerFactory() { return null; } @Override public boolean isOutputReady() { return !sent; } @Override public void produceOutput() throws IOException, HttpException { if (!sent) { channel.submit(headers, endStream); sent = true; } } @Override public void consumePromise(final List
headers) { } @Override public void consumeHeader(final List
headers, final boolean endStream) { } @Override public void updateInputCapacity() { } @Override public void consumeData(final ByteBuffer src, final boolean endStream) { } @Override public void handle(final org.apache.hc.core5.http.HttpException ex, final boolean endStream) throws org.apache.hc.core5.http.HttpException, IOException { throw ex; } @Override public void failed(final Exception cause) { } @Override public void releaseResources() { } } // Small struct + parser to decode the frames we capture from writes private static final class FrameStub { final int type; final int streamId; FrameStub(final int type, final int streamId) { this.type = type; this.streamId = streamId; } } private static List parseFrames(final byte[] all) { final List out = new ArrayList<>(); int p = 0; while (p + 9 <= all.length) { final int len = ((all[p] & 0xff) << 16) | ((all[p + 1] & 0xff) << 8) | (all[p + 2] & 0xff); final int type = all[p + 3] & 0xff; final int sid = ((all[p + 5] & 0x7f) << 24) | ((all[p + 6] & 0xff) << 16) | ((all[p + 7] & 0xff) << 8) | (all[p + 8] & 0xff); p += 9; if (p + len > all.length) break; out.add(new FrameStub(type, sid)); p += len; } return out; } // 2) Client emits PRIORITY_UPDATE BEFORE HEADERS when Priority header present @Test void testSubmitWithPriorityHeaderEmitsPriorityUpdateBeforeHeaders() throws Exception { // Capture writes final List writes = new ArrayList<>(); Mockito.when(protocolIOSession.write(ArgumentMatchers.any(ByteBuffer.class))) .thenAnswer(inv -> { final ByteBuffer b = inv.getArgument(0, ByteBuffer.class); final byte[] copy = new byte[b.remaining()]; b.get(copy); writes.add(copy); return copy.length; }); Mockito.doNothing().when(protocolIOSession).setEvent(ArgumentMatchers.anyInt()); Mockito.doNothing().when(protocolIOSession).clearEvent(ArgumentMatchers.anyInt()); final H2Config h2Config = H2Config.custom().build(); final H2StreamMultiplexerImpl mux = new H2StreamMultiplexerImpl( protocolIOSession, FRAME_FACTORY, StreamIdGenerator.ODD, httpProcessor, CharCodingConfig.DEFAULT, h2Config, h2StreamListener, () -> streamHandler); // Start connection (sends our SETTINGS) mux.onConnect(); writes.clear(); // ignore noise from onConnect // Pretend server SETTINGS includes NO_RFC7540=1 (opts in to new scheme) final WritableByteChannelMock writable = new WritableByteChannelMock(256); final FrameOutputBuffer fob = new FrameOutputBuffer(16 * 1024); final ByteBuffer pl = ByteBuffer.allocate(6); pl.putShort((short) 0x0009); // SETTINGS_NO_RFC7540_PRIORITIES pl.putInt(1); pl.flip(); final RawFrame incomingSettings = new RawFrame(FrameType.SETTINGS.getValue(), 0, 0, pl); fob.write(incomingSettings, writable); mux.onInput(ByteBuffer.wrap(writable.toByteArray())); writes.clear(); // drop the ACK we sent // Create a locally-initiated stream and a handler that will submit headers with Priority final H2StreamChannel ch = mux.createChannel(1); final List
reqHeaders = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "https"), new BasicHeader(":path", "/"), new BasicHeader(":authority", "example.test"), new BasicHeader(HttpHeaders.PRIORITY, "u=3,i") ); mux.createStream(ch, new PriorityHeaderSender(ch, reqHeaders, true)); // Drive output so the handler submits mux.onOutput(); // Stitch captured bytes and parse frames final int total = writes.stream().mapToInt(a -> a.length).sum(); final byte[] all = new byte[total]; int pos = 0; for (final byte[] a : writes) { System.arraycopy(a, 0, all, pos, a.length); pos += a.length; } final List frames = parseFrames(all); // Find first PRIORITY_UPDATE (type 0x10, sid 0) and first HEADERS for stream 1 int idxPriUpd = -1, idxHeaders = -1; for (int i = 0; i < frames.size(); i++) { final FrameStub f = frames.get(i); if (idxPriUpd < 0 && f.type == FrameType.PRIORITY_UPDATE.getValue() && f.streamId == 0) idxPriUpd = i; if (idxHeaders < 0 && f.type == FrameType.HEADERS.getValue() && f.streamId == 1) idxHeaders = i; } Assertions.assertTrue(idxPriUpd >= 0, "PRIORITY_UPDATE not emitted"); Assertions.assertTrue(idxHeaders >= 0, "HEADERS not emitted"); Assertions.assertTrue(idxPriUpd < idxHeaders, "PRIORITY_UPDATE must precede HEADERS"); } // 3) Optional policy: suppress emission when peer’s first SETTINGS omits 0x9 @Test void testPriorityUpdateSuppressedAfterSettingsWithoutNoH2() throws Exception { final List writes = new ArrayList<>(); Mockito.when(protocolIOSession.write(ArgumentMatchers.any(ByteBuffer.class))) .thenAnswer(inv -> { final ByteBuffer b = inv.getArgument(0, ByteBuffer.class); final byte[] c = new byte[b.remaining()]; b.get(c); writes.add(c); return c.length; }); Mockito.doNothing().when(protocolIOSession).setEvent(ArgumentMatchers.anyInt()); Mockito.doNothing().when(protocolIOSession).clearEvent(ArgumentMatchers.anyInt()); final H2StreamMultiplexerImpl mux = new H2StreamMultiplexerImpl( protocolIOSession, FRAME_FACTORY, StreamIdGenerator.ODD, httpProcessor, CharCodingConfig.DEFAULT, H2Config.custom().build(), h2StreamListener, () -> streamHandler); mux.onConnect(); writes.clear(); // Server SETTINGS without 0x9 final WritableByteChannelMock w = new WritableByteChannelMock(256); final FrameOutputBuffer fob = new FrameOutputBuffer(16 * 1024); fob.write(new RawFrame(FrameType.SETTINGS.getValue(), 0, 0, ByteBuffer.allocate(0)), w); mux.onInput(ByteBuffer.wrap(w.toByteArray())); writes.clear(); // drop our ACK // Create local stream that will send Priority header final H2StreamChannel ch = mux.createChannel(1); final List
reqHeaders = Arrays.asList( new BasicHeader(":method","GET"), new BasicHeader(":scheme","https"), new BasicHeader(":path","/"), new BasicHeader(":authority","example.test"), new BasicHeader(HttpHeaders.PRIORITY, "u=3") ); mux.createStream(ch, new PriorityHeaderSender(ch, reqHeaders, true)); mux.onOutput(); final int total = writes.stream().mapToInt(a -> a.length).sum(); final byte[] all = new byte[total]; int p = 0; for (final byte[] a : writes) { System.arraycopy(a, 0, all, p, a.length); p += a.length; } final List frames = parseFrames(all); Assertions.assertTrue(frames.stream().noneMatch(f -> f.type == FrameType.PRIORITY_UPDATE.getValue()), "PRIORITY_UPDATE must be suppressed when peer didn't send NO_RFC7540 (policy)"); } // 4) Continue emission when peer sends NO_RFC7540=1 @Test void testPriorityUpdateContinuesAfterSettingsWithNoH2Equals1() throws Exception { final List writes = new ArrayList<>(); Mockito.when(protocolIOSession.write(ArgumentMatchers.any(ByteBuffer.class))) .thenAnswer(inv -> { final ByteBuffer b = inv.getArgument(0, ByteBuffer.class); final byte[] c = new byte[b.remaining()]; b.get(c); writes.add(c); return c.length; }); Mockito.doNothing().when(protocolIOSession).setEvent(ArgumentMatchers.anyInt()); Mockito.doNothing().when(protocolIOSession).clearEvent(ArgumentMatchers.anyInt()); final H2StreamMultiplexerImpl mux = new H2StreamMultiplexerImpl( protocolIOSession, FRAME_FACTORY, StreamIdGenerator.ODD, httpProcessor, CharCodingConfig.DEFAULT, H2Config.custom().build(), h2StreamListener, () -> streamHandler); mux.onConnect(); writes.clear(); // Server SETTINGS with 0x9 = 1 final WritableByteChannelMock w = new WritableByteChannelMock(256); final FrameOutputBuffer fob = new FrameOutputBuffer(16 * 1024); final ByteBuffer pl = ByteBuffer.allocate(6); pl.putShort((short) 0x0009); pl.putInt(1); pl.flip(); fob.write(new RawFrame(FrameType.SETTINGS.getValue(), 0, 0, pl), w); mux.onInput(ByteBuffer.wrap(w.toByteArray())); writes.clear(); // drop ACK final H2StreamChannel ch = mux.createChannel(1); final List
reqHeaders = Arrays.asList( new BasicHeader(":method","GET"), new BasicHeader(":scheme","https"), new BasicHeader(":path","/"), new BasicHeader(":authority","example.test"), new BasicHeader(HttpHeaders.PRIORITY, "u=0,i") ); mux.createStream(ch, new PriorityHeaderSender(ch, reqHeaders, true)); mux.onOutput(); final int total = writes.stream().mapToInt(a -> a.length).sum(); final byte[] all = new byte[total]; int p = 0; for (final byte[] a : writes) { System.arraycopy(a, 0, all, p, a.length); p += a.length; } final List frames = parseFrames(all); final int idxPriUpd = IntStream.range(0, frames.size()) .filter(i -> frames.get(i).type == FrameType.PRIORITY_UPDATE.getValue() && frames.get(i).streamId == 0) .findFirst().orElse(-1); Assertions.assertTrue(idxPriUpd >= 0, "PRIORITY_UPDATE should be emitted when NO_RFC7540=1"); } @Test void testHPackDecoderUsesConfiguredMaxHeaderListSizeImmediately() throws Exception { final int maxHeaderListSize = 128; final H2Config h2Config = H2Config.custom() .setMaxHeaderListSize(maxHeaderListSize) .build(); final ProtocolIOSession ioSession = Mockito.mock(ProtocolIOSession.class); final HttpProcessor httpProcessor = Mockito.mock(HttpProcessor.class); final HandlerFactory handlerFactory = Mockito.mock(HandlerFactory.class); final ServerH2StreamMultiplexer multiplexer = new ServerH2StreamMultiplexer( ioSession, httpProcessor, handlerFactory, CharCodingConfig.DEFAULT, h2Config); final HPackDecoder hPackDecoder = getHPackDecoder(multiplexer); Assertions.assertEquals(maxHeaderListSize, getMaxListSize(hPackDecoder)); } private static HPackDecoder getHPackDecoder(final AbstractH2StreamMultiplexer multiplexer) throws Exception { final Field field = AbstractH2StreamMultiplexer.class.getDeclaredField("hPackDecoder"); field.setAccessible(true); return (HPackDecoder) field.get(multiplexer); } private static int getMaxListSize(final HPackDecoder hPackDecoder) throws Exception { final Field field = HPackDecoder.class.getDeclaredField("maxListSize"); field.setAccessible(true); return field.getInt(hPackDecoder); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestFrameInOutBuffers.java0100644 0000000 0000000 00000034216 00000250600 027743 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.impl.nio; import java.nio.ByteBuffer; import org.apache.hc.core5.http2.H2ConnectionException; import org.apache.hc.core5.http2.H2CorruptFrameException; import org.apache.hc.core5.http2.ReadableByteChannelMock; import org.apache.hc.core5.http2.WritableByteChannelMock; import org.apache.hc.core5.http2.frame.FrameConsts; import org.apache.hc.core5.http2.frame.FrameFlag; import org.apache.hc.core5.http2.frame.FrameType; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.impl.BasicH2TransportMetrics; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestFrameInOutBuffers { @Test void testReadWriteFrame() throws Exception { final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); final FrameOutputBuffer outbuffer = new FrameOutputBuffer(16 * 1024); final RawFrame frame = new RawFrame(FrameType.DATA.getValue(), 0, 1, ByteBuffer.wrap(new byte[]{1,2,3,4,5})); outbuffer.write(frame, writableChannel); final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final byte[] bytes = writableChannel.toByteArray(); Assertions.assertEquals(FrameConsts.HEAD_LEN + 5, bytes.length); Assertions.assertEquals(1, outbuffer.getMetrics().getFramesTransferred()); Assertions.assertEquals(bytes.length, outbuffer.getMetrics().getBytesTransferred()); final ReadableByteChannelMock readableChannel = new ReadableByteChannelMock(bytes); final RawFrame frame2 = inBuffer.read(readableChannel); Assertions.assertEquals(FrameType.DATA.getValue(), frame2.getType()); Assertions.assertEquals(0, frame2.getFlags()); Assertions.assertEquals(1L, frame2.getStreamId()); final ByteBuffer payload2 = frame2.getPayloadContent(); Assertions.assertNotNull(payload2); Assertions.assertEquals(5, payload2.remaining()); Assertions.assertEquals(1, payload2.get()); Assertions.assertEquals(2, payload2.get()); Assertions.assertEquals(3, payload2.get()); Assertions.assertEquals(4, payload2.get()); Assertions.assertEquals(5, payload2.get()); Assertions.assertEquals(-1, readableChannel.read(ByteBuffer.allocate(1024))); Assertions.assertEquals(1, inBuffer.getMetrics().getFramesTransferred()); Assertions.assertEquals(bytes.length, inBuffer.getMetrics().getBytesTransferred()); final RawFrame frame3 = inBuffer.read(ByteBuffer.wrap(bytes), readableChannel); Assertions.assertEquals(FrameType.DATA.getValue(), frame3.getType()); Assertions.assertEquals(0, frame3.getFlags()); Assertions.assertEquals(1L, frame3.getStreamId()); final ByteBuffer payload3 = frame3.getPayloadContent(); Assertions.assertNotNull(payload3); Assertions.assertEquals(5, payload3.remaining()); Assertions.assertEquals(1, payload3.get()); Assertions.assertEquals(2, payload3.get()); Assertions.assertEquals(3, payload3.get()); Assertions.assertEquals(4, payload3.get()); Assertions.assertEquals(5, payload3.get()); Assertions.assertEquals(2, inBuffer.getMetrics().getFramesTransferred()); Assertions.assertEquals(bytes.length * 2, inBuffer.getMetrics().getBytesTransferred()); } @Test void testPartialFrameWrite() throws Exception { final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024, FrameConsts.HEAD_LEN + 10); final FrameOutputBuffer outbuffer = new FrameOutputBuffer(16 * 1024); final RawFrame frame = new RawFrame(FrameType.DATA.getValue(), FrameFlag.END_STREAM.getValue(), 5, ByteBuffer.wrap(new byte[]{'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'})); outbuffer.write(frame, writableChannel); Assertions.assertArrayEquals(new byte[] {0,0,16,0,1,0,0,0,5,48,49,50,51,52,53,54,55,56,57}, writableChannel.toByteArray()); Assertions.assertFalse(outbuffer.isEmpty()); outbuffer.flush(writableChannel); Assertions.assertArrayEquals(new byte[] {0,0,16,0,1,0,0,0,5,48,49,50,51,52,53,54,55,56,57}, writableChannel.toByteArray()); writableChannel.flush(); outbuffer.flush(writableChannel); Assertions.assertArrayEquals(new byte[] {0,0,16,0,1,0,0,0,5,48,49,50,51,52,53,54,55,56,57,97,98,99,100,101,102}, writableChannel.toByteArray()); } @Test void testReadFrameMultiple() throws Exception { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final ReadableByteChannelMock readableChannel = new ReadableByteChannelMock( new byte[] { 0,0,10,0,8,0,0,0,8,4,0,1,2,3,4,0,0,0,0, 0,0,10,0,9,0,0,0,8,4,5,6,7,8,9,0,0,0,0 }); final RawFrame frame1 = inBuffer.read(readableChannel); Assertions.assertEquals(FrameType.DATA, FrameType.valueOf(frame1.getType())); Assertions.assertEquals(8, frame1.getFlags()); Assertions.assertEquals(8, frame1.getStreamId()); final ByteBuffer payload1 = frame1.getPayloadContent(); Assertions.assertNotNull(payload1); Assertions.assertEquals(5, payload1.remaining()); Assertions.assertEquals(0, payload1.get()); Assertions.assertEquals(1, payload1.get()); Assertions.assertEquals(2, payload1.get()); Assertions.assertEquals(3, payload1.get()); Assertions.assertEquals(4, payload1.get()); final RawFrame frame2 = inBuffer.read(readableChannel); Assertions.assertEquals(FrameType.DATA, FrameType.valueOf(frame2.getType())); Assertions.assertEquals(FrameFlag.of(FrameFlag.END_STREAM, FrameFlag.PADDED), frame2.getFlags()); Assertions.assertEquals(8, frame2.getStreamId()); final ByteBuffer payload2 = frame2.getPayloadContent(); Assertions.assertNotNull(payload2); Assertions.assertEquals(5, payload2.remaining()); Assertions.assertEquals(5, payload2.get()); Assertions.assertEquals(6, payload2.get()); Assertions.assertEquals(7, payload2.get()); Assertions.assertEquals(8, payload2.get()); Assertions.assertEquals(9, payload2.get()); Assertions.assertEquals(-1, readableChannel.read(ByteBuffer.allocate(1024))); } @Test void testReadFrameMultipleSmallBuffer() throws Exception { final FrameInputBuffer inBuffer = new FrameInputBuffer(new BasicH2TransportMetrics(), 20, 10); final ReadableByteChannelMock readableChannel = new ReadableByteChannelMock( new byte[] { 0,0,10,0,8,0,0,0,8,4,1,1,1,1,1,0,0,0,0, 0,0,5,0,0,0,0,0,8,2,2,2,2,2, 0,0,10,0,9,0,0,0,8,4,3,3,3,3,3,0,0,0,0 }); final RawFrame frame1 = inBuffer.read(readableChannel); Assertions.assertEquals(FrameType.DATA, FrameType.valueOf(frame1.getType())); Assertions.assertEquals(8, frame1.getFlags()); Assertions.assertEquals(8, frame1.getStreamId()); final ByteBuffer payload1 = frame1.getPayloadContent(); Assertions.assertNotNull(payload1); Assertions.assertEquals(5, payload1.remaining()); Assertions.assertEquals(1, payload1.get()); Assertions.assertEquals(1, payload1.get()); Assertions.assertEquals(1, payload1.get()); Assertions.assertEquals(1, payload1.get()); Assertions.assertEquals(1, payload1.get()); final RawFrame frame2 = inBuffer.read(readableChannel); Assertions.assertEquals(FrameType.DATA, FrameType.valueOf(frame2.getType())); Assertions.assertEquals(0, frame2.getFlags()); Assertions.assertEquals(8, frame2.getStreamId()); final ByteBuffer payload2 = frame2.getPayloadContent(); Assertions.assertNotNull(payload2); Assertions.assertEquals(5, payload2.remaining()); Assertions.assertEquals(2, payload2.get()); Assertions.assertEquals(2, payload2.get()); Assertions.assertEquals(2, payload2.get()); Assertions.assertEquals(2, payload2.get()); Assertions.assertEquals(2, payload2.get()); final RawFrame frame3 = inBuffer.read(readableChannel); Assertions.assertEquals(FrameType.DATA, FrameType.valueOf(frame3.getType())); Assertions.assertEquals(FrameFlag.of(FrameFlag.END_STREAM, FrameFlag.PADDED), frame3.getFlags()); Assertions.assertEquals(8, frame3.getStreamId()); final ByteBuffer payload3 = frame3.getPayloadContent(); Assertions.assertNotNull(payload3); Assertions.assertEquals(5, payload3.remaining()); Assertions.assertEquals(3, payload3.get()); Assertions.assertEquals(3, payload3.get()); Assertions.assertEquals(3, payload3.get()); Assertions.assertEquals(3, payload3.get()); Assertions.assertEquals(3, payload3.get()); Assertions.assertEquals(-1, readableChannel.read(ByteBuffer.allocate(1024))); } @Test void testReadFramePartialReads() throws Exception { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final ReadableByteChannelMock readableChannel = new ReadableByteChannelMock( new byte[] {0,0}, new byte[] {10,0,9,0}, new byte[] {0,0,8}, new byte[] {4}, new byte[] {1,2,3,4}, new byte[] {5,0}, new byte[] {0,0,0}); final RawFrame frame = inBuffer.read(readableChannel); Assertions.assertEquals(FrameType.DATA, FrameType.valueOf(frame.getType())); Assertions.assertEquals(FrameFlag.of(FrameFlag.END_STREAM, FrameFlag.PADDED), frame.getFlags()); Assertions.assertEquals(8, frame.getStreamId()); final ByteBuffer payload = frame.getPayloadContent(); Assertions.assertNotNull(payload); Assertions.assertEquals(5, payload.remaining()); Assertions.assertEquals(1, payload.get()); Assertions.assertEquals(2, payload.get()); Assertions.assertEquals(3, payload.get()); Assertions.assertEquals(4, payload.get()); Assertions.assertEquals(5, payload.get()); Assertions.assertEquals(-1, readableChannel.read(ByteBuffer.allocate(1024))); } @Test void testReadEmptyFrame() throws Exception { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final ReadableByteChannelMock readableChannel = new ReadableByteChannelMock( new byte[] {0,0,0,0,0,0,0,0,0}); final RawFrame frame = inBuffer.read(readableChannel); Assertions.assertEquals(FrameType.DATA, FrameType.valueOf(frame.getType())); Assertions.assertEquals(0, frame.getFlags()); Assertions.assertEquals(0, frame.getStreamId()); final ByteBuffer payload = frame.getPayloadContent(); Assertions.assertNull(payload); } @Test void testReadFrameConnectionClosed() throws Exception { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final ReadableByteChannelMock readableChannel = new ReadableByteChannelMock(new byte[] {}); Assertions.assertNull(inBuffer.read(readableChannel)); Assertions.assertFalse(inBuffer.isEndOfStream()); Assertions.assertNull(inBuffer.read(readableChannel)); Assertions.assertTrue(inBuffer.isEndOfStream()); Assertions.assertNull(inBuffer.read(readableChannel)); Assertions.assertTrue(inBuffer.isEndOfStream()); } @Test void testReadFrameCorruptFrame() { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final ReadableByteChannelMock readableChannel = new ReadableByteChannelMock(new byte[] {0,0}); Assertions.assertThrows(H2CorruptFrameException.class, () -> inBuffer.read(readableChannel)); } @Test void testWriteFrameExceedingLimit() { final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); final FrameOutputBuffer outbuffer = new FrameOutputBuffer(1024); final RawFrame frame = new RawFrame(FrameType.DATA.getValue(), 0, 1, ByteBuffer.wrap(new byte[2048])); Assertions.assertThrows(IllegalArgumentException.class, () -> outbuffer.write(frame, writableChannel)); } @Test void testReadFrameExceedingLimit() { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final ReadableByteChannelMock readableChannel = new ReadableByteChannelMock( new byte[] {0,-128,-128,0,0,0,0,0,1}); Assertions.assertThrows(H2ConnectionException.class, () -> inBuffer.read(readableChannel)); } @Test void testOutputBufferResize() { final FrameOutputBuffer outBuffer = new FrameOutputBuffer(16 * 1024); Assertions.assertEquals(16 * 1024, outBuffer.getMaxFramePayloadSize()); outBuffer.resize(1024); Assertions.assertEquals(1024, outBuffer.getMaxFramePayloadSize()); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/priority/0040755 0000000 0000000 00000000000 00000250600 023042 5ustar000000000 0000000 httpcore5-h2/src/test/java/org/apache/hc/core5/http2/priority/TestPriorityMerge.java0100644 0000000 0000000 00000010705 00000250600 027346 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.priority; 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 org.junit.jupiter.api.Test; final class TestPriorityMerge { @Test void rfc_example_server_overrides_urgency_preserves_incremental() { // Client: u=5, i=true final PriorityValue clientReq = PriorityValue.of(5, true); // Server response: u=1 (i omitted => server does not change it) final PriorityParams serverResp = PriorityParamsParser.parse("u=1"); final PriorityValue merged = PriorityMerge.merge(clientReq, serverResp); assertEquals(1, merged.getUrgency()); assertTrue(merged.isIncremental()); } @Test void response_absent_keeps_client_values() { final PriorityValue clientReq = PriorityValue.of(2, false); final PriorityValue merged = PriorityMerge.merge(clientReq, null); assertEquals(2, merged.getUrgency()); assertFalse(merged.isIncremental()); } @Test void client_absent_uses_response_or_defaults() { // Only server response provided final PriorityParams resp1 = PriorityParamsParser.parse("u=0, i"); final PriorityValue merged1 = PriorityMerge.merge(null, resp1); assertEquals(0, merged1.getUrgency()); assertTrue(merged1.isIncremental()); // Neither side provided => defaults final PriorityValue merged2 = PriorityMerge.merge(null, null); assertEquals(PriorityValue.DEFAULT_URGENCY, merged2.getUrgency()); assertEquals(PriorityValue.DEFAULT_INCREMENTAL, merged2.isIncremental()); } @Test void server_sets_only_incremental_true_keeps_client_urgency() { final PriorityValue clientReq = PriorityValue.of(4, false); final PriorityParams serverResp = PriorityParamsParser.parse("i"); final PriorityValue merged = PriorityMerge.merge(clientReq, serverResp); assertEquals(4, merged.getUrgency()); assertTrue(merged.isIncremental()); } @Test void server_sets_only_urgency_keeps_client_incremental() { final PriorityValue clientReq = PriorityValue.of(6, true); final PriorityParams serverResp = PriorityParamsParser.parse("u=1"); final PriorityValue merged = PriorityMerge.merge(clientReq, serverResp); assertEquals(1, merged.getUrgency()); assertTrue(merged.isIncremental()); } @Test void out_of_range_server_urgency_is_ignored_but_other_members_apply() { final PriorityValue clientReq = PriorityValue.of(3, false); // u=9 is invalid -> ignored; i applies final PriorityParams serverResp = PriorityParamsParser.parse("u=9, i"); final PriorityValue merged = PriorityMerge.merge(clientReq, serverResp); assertEquals(3, merged.getUrgency()); // unchanged assertTrue(merged.isIncremental()); // from server } @Test void null_safety_with_valid_inputs() { // Server provides only u; client null -> defaults for i final PriorityValue merged = PriorityMerge.merge(null, new PriorityParams(2, null)); assertEquals(2, merged.getUrgency()); assertEquals(PriorityValue.DEFAULT_INCREMENTAL, merged.isIncremental()); } }httpcore5-h2/src/test/java/org/apache/hc/core5/http2/priority/TestPriorityParamsParser.java0100644 0000000 0000000 00000005035 00000250600 030707 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.priority; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; final class TestPriorityParamsParser { @Test void captures_presence_correctly() { final PriorityParams p1 = PriorityParamsParser.parse("u=1"); assertEquals(1, p1.getUrgency()); assertNull(p1.getIncremental()); final PriorityParams p2 = PriorityParamsParser.parse("i"); assertNull(p2.getUrgency()); assertEquals(Boolean.TRUE, p2.getIncremental()); final PriorityParams p3 = PriorityParamsParser.parse("u=5, i"); assertEquals(5, p3.getUrgency()); assertEquals(Boolean.TRUE, p3.getIncremental()); } @Test void ignores_unknown_and_out_of_range() { final PriorityParams p = PriorityParamsParser.parse("foo=bar, u=9, i=?0"); assertNull(p.getUrgency()); // out-of-range ignored => absent assertEquals(Boolean.FALSE, p.getIncremental()); } @Test void toValueWithDefaults_applies_defaults() { final PriorityParams p = PriorityParamsParser.parse("i"); final PriorityValue v = p.toValueWithDefaults(); assertEquals(3, v.getUrgency()); assertTrue(v.isIncremental()); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/priority/TestPriorityParserAndFormatter.java0100644 0000000 0000000 00000014202 00000250600 032046 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.priority; 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; import org.apache.hc.core5.http.Header; import org.junit.jupiter.api.Test; public final class TestPriorityParserAndFormatter { // ---- RFC 9218 examples -------------------------------------------------- @Test void rfc_example_css_urgency0() { // From RFC 9218 §4.1: priority = u=0 final PriorityValue v = PriorityParser.parse("u=0"); assertEquals(0, v.getUrgency()); assertFalse(v.isIncremental()); // Formatter emits only non-defaults assertEquals("u=0", PriorityFormatter.format(v)); } @Test void rfc_example_jpeg_u5_incremental() { // From RFC 9218 §4.2: priority = u=5, i final PriorityValue v = PriorityParser.parse("u=5, i"); assertEquals(5, v.getUrgency()); assertTrue(v.isIncremental()); // Formatter keeps canonical "u=5, i" assertEquals("u=5, i", PriorityFormatter.format(v)); } // ---- Defaults & omission ------------------------------------------------- @Test void defaults_when_empty_or_null() { final PriorityValue v1 = PriorityParser.parse(""); assertEquals(3, v1.getUrgency()); assertFalse(v1.isIncremental()); final PriorityValue v2 = PriorityParser.parse((Header) null); assertEquals(3, v2.getUrgency()); assertFalse(v2.isIncremental()); } // ---- Boolean variants per RFC 8941 -------------------------------------- @Test void boolean_variants_for_incremental() { assertTrue(PriorityParser.parse("i").isIncremental()); // bare true assertTrue(PriorityParser.parse("i=?1").isIncremental()); // structured boolean true assertFalse(PriorityParser.parse("i=?0").isIncremental()); // structured boolean false assertTrue(PriorityParser.parse("i=1").isIncremental()); // tolerant numeric '1' assertFalse(PriorityParser.parse("i=0").isIncremental()); // tolerant numeric '0' } // ---- Unknown & invalid handling ----------------------------------------- @Test void unknown_members_are_ignored() { final PriorityValue v = PriorityParser.parse("foo=bar, u=2, i, baz=?1"); assertEquals(2, v.getUrgency()); assertTrue(v.isIncremental()); } @Test void urgency_out_of_range_is_ignored() { final PriorityValue v1 = PriorityParser.parse("u=9"); // >7 assertEquals(3, v1.getUrgency()); final PriorityValue v2 = PriorityParser.parse("u=-1"); // <0 assertEquals(3, v2.getUrgency()); } @Test void malformed_members_are_ignored() { final PriorityValue v = PriorityParser.parse("u=abc, i=banana, i=?x"); assertEquals(3, v.getUrgency()); // default assertFalse(v.isIncremental()); // default } // ---- Whitespace, params, case-insensitivity ----------------------------- @Test void handles_ows_and_parameters_and_case() { // Ignore structured-field parameters after members, and key is case-insensitive final PriorityValue v = PriorityParser.parse(" U = 1 ;p=v , I ;x "); assertEquals(1, v.getUrgency()); assertTrue(v.isIncremental()); // Formatter canonicalizes output (no params, normalized layout) assertEquals("u=1, i", PriorityFormatter.format(v)); } // ---- Minimal formatting rules ------------------------------------------- @Test void formatter_emits_only_non_defaults_in_canonical_order() { assertEquals("u=1", PriorityFormatter.format(PriorityValue.of(1, false))); assertEquals("i", PriorityFormatter.format(PriorityValue.of(3, true))); assertEquals("u=2, i", PriorityFormatter.format(PriorityValue.of(2, true))); } // ---- Round-trips --------------------------------------------------------- @Test void round_trip_common_values() { roundTrip("u=0"); roundTrip("u=0, i"); roundTrip("u=5, i"); roundTrip("i"); roundTrip("u=7"); } private static void roundTrip(final String s) { final PriorityValue v = PriorityParser.parse(s); final String out = PriorityFormatter.format(v); // If v equals defaults, formatter returns null; otherwise we expect the canonical equivalent if (v.getUrgency() == 3 && !v.isIncremental()) { assertNull(out); } else { // Canonical ordering is u first (if non-default), then i if true if (v.getUrgency() != 3 && v.isIncremental()) { assertEquals("u=" + v.getUrgency() + ", i", out); } else if (v.getUrgency() != 3) { assertEquals("u=" + v.getUrgency(), out); } else { assertEquals("i", out); } } } }httpcore5-h2/src/test/java/org/apache/hc/core5/http2/priority/TestPriorityValue.java0100644 0000000 0000000 00000011130 00000250600 027354 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.priority; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; final class TestPriorityValue { @Test void defaultsAreAsSpecified() { final PriorityValue v = PriorityValue.defaults(); assertEquals(3, v.getUrgency()); assertFalse(v.isIncremental()); } @Test void factoryOfBuildsEquivalentInstances() { final PriorityValue v1 = new PriorityValue(0, true); final PriorityValue v2 = PriorityValue.of(0, true); assertEquals(v1, v2); assertEquals(v1.hashCode(), v2.hashCode()); } @Test void acceptsFullValidRangeZeroToSeven() { for (int u = 0; u <= 7; u++) { final PriorityValue v = PriorityValue.of(u, false); assertEquals(u, v.getUrgency()); assertFalse(v.isIncremental()); } } @Test void rejectsUrgencyBelowZero() { final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> new PriorityValue(-1, false)); assertTrue(ex.getMessage().toLowerCase().contains("range")); } @Test void rejectsUrgencyAboveSeven() { final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> PriorityValue.of(8, true)); assertTrue(ex.getMessage().toLowerCase().contains("range")); } @Test void withersReturnNewInstancesAndDoNotMutate() { final PriorityValue base = PriorityValue.of(3, false); final PriorityValue u0 = base.withUrgency(0); final PriorityValue inc = base.withIncremental(true); // base remains unchanged assertEquals(3, base.getUrgency()); assertFalse(base.isIncremental()); // new values applied assertEquals(0, u0.getUrgency()); assertFalse(u0.isIncremental()); assertEquals(3, inc.getUrgency()); assertTrue(inc.isIncremental()); // withers create distinct instances when changing a field assertNotSame(base, u0); assertNotSame(base, inc); } @Test void withUrgencyValidatesRange() { final PriorityValue base = PriorityValue.defaults(); assertThrows(IllegalArgumentException.class, () -> base.withUrgency(-1)); assertThrows(IllegalArgumentException.class, () -> base.withUrgency(9)); } @Test void equalityAndHashAreBasedOnFields() { final PriorityValue a = PriorityValue.of(2, true); final PriorityValue b = PriorityValue.of(2, true); final PriorityValue c = PriorityValue.of(2, false); final PriorityValue d = PriorityValue.of(3, true); assertEquals(a, b); assertEquals(a.hashCode(), b.hashCode()); assertNotEquals(a, c); assertNotEquals(a, d); assertNotEquals(c, d); } @Test void toStringIncludesFields() { final PriorityValue v = PriorityValue.of(1, true); final String s = v.toString(); assertNotNull(s); assertTrue(s.contains("u=1")); assertTrue(s.contains("i=true")); } }httpcore5-h2/src/test/java/org/apache/hc/core5/http2/protocol/0040755 0000000 0000000 00000000000 00000250600 023022 5ustar000000000 0000000 httpcore5-h2/src/test/java/org/apache/hc/core5/http2/protocol/TestH2Interceptors.java0100644 0000000 0000000 00000027536 00000250600 027412 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.protocol; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.io.entity.HttpEntities; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class TestH2Interceptors { /** * HTTP context. */ private HttpCoreContext context; @BeforeEach void setUp() { context = HttpCoreContext.create(); context.setProtocolVersion(HttpVersion.HTTP_2); } @Test void testH2RequestContentProtocolException() { final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.TRACE, "/"); request.addHeader(new BasicHeader(HttpHeaders.TRANSFER_ENCODING, "chunked")); request.setEntity(new StringEntity("whatever", StandardCharsets.US_ASCII)); final HttpRequestInterceptor interceptor = H2RequestContent.INSTANCE; Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request, request.getEntity(), context)); } @Test void testH2RequestContentNullEntity() throws Exception { final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); final HttpRequestInterceptor interceptor = H2RequestContent.INSTANCE; interceptor.process(request, request.getEntity(), context); final Header header = request.getFirstHeader(HttpHeaders.CONTENT_LENGTH); Assertions.assertNull(header); Assertions.assertNull(request.getFirstHeader(HttpHeaders.TRANSFER_ENCODING)); } @Test void testH2RequestContentValid() throws Exception { final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); // Create a mock entity with ContentType, ContentEncoding, and TrailerNames request.setEntity(HttpEntities.create("whatever", StandardCharsets.US_ASCII, new BasicHeader("h1", "this"), new BasicHeader("h1", "that"), new BasicHeader("h2", "this and that"))); final HttpRequestInterceptor interceptor = H2RequestContent.INSTANCE; interceptor.process(request, request.getEntity(), context); // Assertions to validate headers final Header header1 = request.getFirstHeader(HttpHeaders.CONTENT_TYPE); Assertions.assertNotNull(header1); final Header header2 = request.getFirstHeader(HttpHeaders.TRAILER); Assertions.assertNotNull(header2); Assertions.assertEquals("h1, h2", header2.getValue()); } @Test void testH2RequestContentOptionMethodNullContentTypeProtocolException() { final H2RequestContent interceptor = new H2RequestContent(); final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.OPTIONS, "/"); request.addHeader(new BasicHeader(HttpHeaders.CONTENT_LENGTH, "10")); request.addHeader(new BasicHeader(HttpHeaders.TRANSFER_ENCODING, "whatever")); request.setEntity(new StringEntity("")); Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request, request.getEntity(), context)); } @Test void testH2RequestContentOptionMethodInvalidContentTypeProtocolException() { final H2RequestContent interceptor = new H2RequestContent(); final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.OPTIONS, "/"); request.addHeader(new BasicHeader(HttpHeaders.CONTENT_LENGTH, "10")); request.addHeader(new BasicHeader(HttpHeaders.TRANSFER_ENCODING, "whatever")); request.setEntity(new StringEntity("")); request.addHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "text/plain; charset=invalid_charset_name!")); Assertions.assertThrows(ProtocolException.class, () -> interceptor.process(request, request.getEntity(), context)); } @Test void testH2RequestContentValidOptionsMethod() throws Exception { final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.OPTIONS, "/"); // Create a mock entity with ContentType, ContentEncoding, and TrailerNames request.setEntity(HttpEntities.create("whatever", StandardCharsets.US_ASCII, new BasicHeader("h1", "this"), new BasicHeader("h1", "that"), new BasicHeader("h2", "this and that"))); final HttpRequestInterceptor interceptor = H2RequestContent.INSTANCE; interceptor.process(request, request.getEntity(), context); // Assertions to validate headers final Header header1 = request.getFirstHeader(HttpHeaders.CONTENT_TYPE); Assertions.assertNotNull(header1); } @Test void testH2RequestConformanceConnectionHeader() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("Connection", "Keep-Alive"); final H2RequestConformance interceptor = new H2RequestConformance(); Assertions.assertThrows(HttpException.class, () -> interceptor.process(request, null, context), "Header 'Connection: Keep-Alive' is illegal for HTTP/2 messages"); } @Test void testH2RequestConformanceKeepAliveHeader() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("Keep-Alive", "timeout=5, max=1000"); final H2RequestConformance interceptor = new H2RequestConformance(); Assertions.assertThrows(HttpException.class, () -> interceptor.process(request, null, context), "Header 'Keep-Alive: timeout=5, max=1000' is illegal for HTTP/2 messages"); } @Test void testH2RequestConformanceProxyConnectionHeader() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("Proxy-Connection", "keep-alive"); final H2RequestConformance interceptor = new H2RequestConformance(); Assertions.assertThrows(HttpException.class, () -> interceptor.process(request, null, context), "Header 'Proxy-Connection: Keep-Alive' is illegal for HTTP/2 messages"); } @Test void testH2RequestConformanceTransferEncodingHeader() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("Transfer-Encoding", "gzip"); final H2RequestConformance interceptor = new H2RequestConformance(); Assertions.assertThrows(HttpException.class, () -> interceptor.process(request, null, context), "Header 'Transfer-Encoding: gzip' is illegal for HTTP/2 messages"); } @Test void testH2RequestConformanceHostHeader() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("Host", "host"); final H2RequestConformance interceptor = new H2RequestConformance(); Assertions.assertDoesNotThrow(() -> interceptor.process(request, null, context), "Header 'Host: host' is permissible for HTTP/2 messages"); } @Test void testH2RequestConformanceUpgradeHeader() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("Upgrade", "example/1, foo/2"); final H2RequestConformance interceptor = new H2RequestConformance(); Assertions.assertThrows(HttpException.class, () -> interceptor.process(request, null, context), "Header 'Upgrade: example/1, foo/2' is illegal for HTTP/2 messages"); } @Test void testH2RequestConformanceTEHeader() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("TE", "gzip"); final H2RequestConformance interceptor = new H2RequestConformance(); Assertions.assertThrows(HttpException.class, () -> interceptor.process(request, null, context), "Header 'TE: gzip' is illegal for HTTP/2 messages"); } @Test void testH2ResponseConformanceConnectionHeader() { final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK); response.addHeader("Connection", "Keep-Alive"); final H2ResponseConformance interceptor = new H2ResponseConformance(); Assertions.assertThrows(HttpException.class, () -> interceptor.process(response, null, context), "Header 'connection: keep-alive' is illegal for HTTP/2 messages"); } @Test void testH2ResponseConformanceKeepAliveHeader() { final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK); response.addHeader("keep-alive", "timeout=5, max=1000"); final H2ResponseConformance interceptor = new H2ResponseConformance(); Assertions.assertThrows(HttpException.class, () -> interceptor.process(response, null, context), "Header 'keep-alive: timeout=5, max=1000' is illegal for HTTP/2 messages"); } @Test void testH2ResponseConformanceTransferEncodingHeader() { final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK); response.addHeader("transfer-encoding", "gzip"); final H2ResponseConformance interceptor = new H2ResponseConformance(); Assertions.assertThrows(HttpException.class, () -> interceptor.process(response, null, context), "Header 'transfer-encoding: gzip' is illegal for HTTP/2 messages"); } @Test void testH2ResponseConformanceUpgradeHeader() { final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK); response.addHeader("upgrade", "example/1, foo/2"); final H2ResponseConformance interceptor = new H2ResponseConformance(); Assertions.assertThrows(HttpException.class, () -> interceptor.process(response, null, context), "Header 'upgrade: example/1, foo/2' is illegal for HTTP/2 messages"); } } httpcore5-h2/src/test/java/org/apache/hc/core5/http2/ssl/0040755 0000000 0000000 00000000000 00000250600 021762 5ustar000000000 0000000 httpcore5-h2/src/test/java/org/apache/hc/core5/http2/ssl/ConscryptClientTlsStrategyTest.java0100644 0000000 0000000 00000004037 00000250600 031017 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.http2.ssl; import static org.junit.jupiter.api.Assertions.assertNotNull; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; class ConscryptClientTlsStrategyTest { @Mock private SSLSessionVerifier sslSessionVerifier; @BeforeEach void prepareMocks() { MockitoAnnotations.openMocks(this); } @Test void test_valid_tls_strategy_creation() { final ConscryptClientTlsStrategy strategy = new ConscryptClientTlsStrategy(); assertNotNull(strategy); } @Test void test_valid_tls_strategy_creation_with_verifier() { final ConscryptClientTlsStrategy strategy = new ConscryptClientTlsStrategy(sslSessionVerifier); assertNotNull(strategy); } }httpcore5-reactive/0040755 0000000 0000000 00000000000 00000250600 013303 5ustar000000000 0000000 httpcore5-reactive/pom.xml0100644 0000000 0000000 00000006010 00000250600 014612 0ustar000000000 0000000 httpcore5-parent org.apache.httpcomponents.core5 5.4.3 4.0.0 httpcore5-reactive Apache HttpComponents Core Reactive Extensions Apache HttpComponents Reactive Streams Bindings org.apache.httpcomponents.core5.httpcore5.reactive org.apache.httpcomponents.core5 httpcore5 org.reactivestreams reactive-streams 1.0.4 org.junit.jupiter junit-jupiter test io.reactivex.rxjava3 rxjava ${rxjava3.version} test maven-project-info-reports-plugin false index dependencies dependency-info summary httpcore5-reactive/src/0040755 0000000 0000000 00000000000 00000250600 014072 5ustar000000000 0000000 httpcore5-reactive/src/main/0040755 0000000 0000000 00000000000 00000250600 015016 5ustar000000000 0000000 httpcore5-reactive/src/main/java/0040755 0000000 0000000 00000000000 00000250600 015737 5ustar000000000 0000000 httpcore5-reactive/src/main/java/org/0040755 0000000 0000000 00000000000 00000250600 016526 5ustar000000000 0000000 httpcore5-reactive/src/main/java/org/apache/0040755 0000000 0000000 00000000000 00000250600 017747 5ustar000000000 0000000 httpcore5-reactive/src/main/java/org/apache/hc/0040755 0000000 0000000 00000000000 00000250600 020341 5ustar000000000 0000000 httpcore5-reactive/src/main/java/org/apache/hc/core5/0040755 0000000 0000000 00000000000 00000250600 021356 5ustar000000000 0000000 httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/0040755 0000000 0000000 00000000000 00000250600 023160 5ustar000000000 0000000 httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveDataConsumer.java0100644 0000000 0000000 00000015021 00000250600 030067 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactive; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpStreamResetException; import org.apache.hc.core5.http.nio.AsyncDataConsumer; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.util.Args; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; /** * An asynchronous data consumer that supports Reactive Streams. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE) final class ReactiveDataConsumer implements AsyncDataConsumer, Publisher { private final AtomicLong requests = new AtomicLong(0); private final BlockingQueue buffers = new LinkedBlockingQueue<>(); private final AtomicBoolean flushInProgress = new AtomicBoolean(); private final AtomicInteger windowScalingIncrement = new AtomicInteger(0); private volatile boolean cancelled; private volatile boolean completed; private volatile Exception exception; private volatile CapacityChannel capacityChannel; private volatile Subscriber subscriber; private final ReentrantLock lock = new ReentrantLock(); public void failed(final Exception cause) { if (!completed) { exception = cause; flushToSubscriber(); } } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { throwIfCancelled(); this.capacityChannel = capacityChannel; signalCapacity(capacityChannel); } private void signalCapacity(final CapacityChannel channel) throws IOException { final int increment = windowScalingIncrement.getAndSet(0); if (increment > 0) { channel.update(increment); } } private void throwIfCancelled() throws IOException { if (cancelled) { throw new HttpStreamResetException("Downstream subscriber to ReactiveDataConsumer cancelled"); } } @Override public void consume(final ByteBuffer byteBuffer) throws IOException { if (completed) { throw new IllegalStateException("Received data past end of stream"); } throwIfCancelled(); final byte[] copy = new byte[byteBuffer.remaining()]; byteBuffer.get(copy); buffers.add(ByteBuffer.wrap(copy)); flushToSubscriber(); } @Override public void streamEnd(final List trailers) { completed = true; flushToSubscriber(); } @Override public void releaseResources() { this.capacityChannel = null; } private void flushToSubscriber() { lock.lock(); try { final Subscriber s = subscriber; if (flushInProgress.getAndSet(true)) { return; } try { if (s == null) { return; } if (exception != null) { subscriber = null; s.onError(exception); return; } ByteBuffer next; while (requests.get() > 0 && (next = buffers.poll()) != null) { final int bytesFreed = next.remaining(); s.onNext(next); requests.decrementAndGet(); windowScalingIncrement.addAndGet(bytesFreed); } final CapacityChannel localChannel = capacityChannel; if (localChannel != null) { try { signalCapacity(localChannel); } catch (final IOException e) { exception = e; s.onError(e); return; } } if (completed && buffers.isEmpty()) { subscriber = null; s.onComplete(); } } finally { flushInProgress.set(false); } } finally { lock.unlock(); } } @Override public void subscribe(final Subscriber subscriber) { this.subscriber = Args.notNull(subscriber, "subscriber"); subscriber.onSubscribe(new Subscription() { @Override public void request(final long increment) { if (increment <= 0) { failed(new IllegalArgumentException("The number of elements requested must be strictly positive")); return; } requests.addAndGet(increment); flushToSubscriber(); } @Override public void cancel() { ReactiveDataConsumer.this.cancelled = true; ReactiveDataConsumer.this.subscriber = null; } }); } } httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveDataProducer.java0100644 0000000 0000000 00000014146 00000250600 030066 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactive; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayDeque; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpStreamResetException; import org.apache.hc.core5.http.nio.AsyncDataProducer; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.util.Args; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; /** * An asynchronous data producer that supports Reactive Streams. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE) final class ReactiveDataProducer implements AsyncDataProducer, Subscriber { private static final int BUFFER_WINDOW_SIZE = 5; private final AtomicReference requestChannel = new AtomicReference<>(); private final AtomicReference exception = new AtomicReference<>(); private final AtomicBoolean complete = new AtomicBoolean(); private final Publisher publisher; private final AtomicReference subscription = new AtomicReference<>(); private final ArrayDeque buffers = new ArrayDeque<>(); // This field requires synchronization private final ReentrantLock lock; public ReactiveDataProducer(final Publisher publisher) { this.publisher = Args.notNull(publisher, "publisher"); this.lock = new ReentrantLock(); } void setChannel(final DataStreamChannel channel) { requestChannel.set(channel); } @Override public void onSubscribe(final Subscription subscription) { if (this.subscription.getAndSet(subscription) != null) { throw new IllegalStateException("Already subscribed"); } subscription.request(BUFFER_WINDOW_SIZE); } @Override public void onNext(final ByteBuffer byteBuffer) { final byte[] copy = new byte[byteBuffer.remaining()]; byteBuffer.get(copy); lock.lock(); try { buffers.add(ByteBuffer.wrap(copy)); } finally { lock.unlock(); } signalReadiness(); } @Override public void onError(final Throwable throwable) { subscription.set(null); exception.set(throwable); signalReadiness(); } @Override public void onComplete() { subscription.set(null); complete.set(true); signalReadiness(); } private void signalReadiness() { final DataStreamChannel channel = requestChannel.get(); if (channel == null) { throw new IllegalStateException("Output channel is not set"); } channel.requestOutput(); } @Override public int available() { if (exception.get() != null || complete.get()) { return 1; } else { lock.lock(); try { int sum = 0; for (final ByteBuffer buffer : buffers) { sum += buffer.remaining(); } return sum; } finally { lock.unlock(); } } } @Override public void produce(final DataStreamChannel channel) throws IOException { if (requestChannel.get() == null) { requestChannel.set(channel); publisher.subscribe(this); } final Throwable t = exception.get(); final Subscription s = subscription.get(); int buffersToReplenish = 0; try { lock.lock(); try { if (t != null) { throw new HttpStreamResetException(t.getMessage(), t); } else if (this.complete.get() && buffers.isEmpty()) { channel.endStream(); } else { while (!buffers.isEmpty()) { final ByteBuffer nextBuffer = buffers.remove(); channel.write(nextBuffer); if (nextBuffer.remaining() > 0) { buffers.push(nextBuffer); break; } else if (s != null) { // We defer the #request call until after we release the buffer lock. buffersToReplenish++; } } } } finally { lock.unlock(); } } finally { if (s != null && buffersToReplenish > 0) { s.request(buffersToReplenish); } } } @Override public void releaseResources() { final Subscription s = subscription.getAndSet(null); if (s != null) { s.cancel(); } } } httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveEntityProducer.java0100644 0000000 0000000 00000007625 00000250600 030475 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactive; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.reactivestreams.Publisher; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Collections; import java.util.Objects; import java.util.Set; /** * An {@link AsyncEntityProducer} that subscribes to a {@code Publisher} * instance, as defined by the Reactive Streams specification. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE) public final class ReactiveEntityProducer implements AsyncEntityProducer { private final ReactiveDataProducer reactiveDataProducer; private final long contentLength; private final ContentType contentType; private final String contentEncoding; /** * Creates a new {@code ReactiveEntityProducer} with the given parameters. * * @param publisher the publisher of the entity stream. * @param contentLength the length of the entity, or -1 if unknown (implies chunked encoding). * @param contentType the {@code Content-Type} of the entity, or null if none. * @param contentEncoding the {@code Content-Encoding} of the entity, or null if none. */ public ReactiveEntityProducer( final Publisher publisher, final long contentLength, final ContentType contentType, final String contentEncoding ) { this.reactiveDataProducer = new ReactiveDataProducer(publisher); this.contentLength = contentLength; this.contentType = contentType; this.contentEncoding = contentEncoding; } @Override public int available() { return reactiveDataProducer.available(); } @Override public void produce(final DataStreamChannel channel) throws IOException { reactiveDataProducer.produce(channel); } @Override public void releaseResources() { reactiveDataProducer.releaseResources(); } @Override public boolean isRepeatable() { return false; } @Override public void failed(final Exception cause) { releaseResources(); } @Override public long getContentLength() { return contentLength; } @Override public String getContentType() { return Objects.toString(contentType, null); } @Override public String getContentEncoding() { return contentEncoding; } @Override public boolean isChunked() { return contentLength == -1; } @Override public Set getTrailerNames() { return Collections.emptySet(); } } httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveRequestProcessor.java0100644 0000000 0000000 00000005320 00000250600 031033 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactive; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.reactivestreams.Publisher; import java.io.IOException; import java.nio.ByteBuffer; /** * @since 5.0 */ public interface ReactiveRequestProcessor { /** * Processes the actual HTTP request. The handler can choose to send * response messages immediately inside the call or asynchronously * at some later point. * * @param request the actual request. * @param entityDetails the request entity details or {@code null} if the request * does not enclose an entity. * @param responseChannel the response channel. * @param context the actual execution context. * @param requestBody a reactive stream representing the request body. * @param responseBodyCallback a callback to invoke with the response body, if any. * @throws HttpException in case of HTTP protocol violation. * @throws IOException in case of an I/O error. */ void processRequest( HttpRequest request, EntityDetails entityDetails, ResponseChannel responseChannel, HttpContext context, Publisher requestBody, Callback> responseBodyCallback) throws HttpException, IOException; } httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveResponseConsumer.java0100644 0000000 0000000 00000014074 00000250600 031023 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactive; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.Future; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.concurrent.BasicFuture; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.nio.AsyncResponseConsumer; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; import org.reactivestreams.Publisher; /** * An {@link AsyncResponseConsumer} that publishes the response body through * a {@link Publisher}, as defined by the Reactive Streams specification. The * response is represented as a {@link Message} consisting of a {@link * HttpResponse} representing the headers and a {@link Publisher} representing * the response body as an asynchronous stream of {@link ByteBuffer} instances. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE) public final class ReactiveResponseConsumer implements AsyncResponseConsumer { private final ReactiveDataConsumer reactiveDataConsumer = new ReactiveDataConsumer(); private final List
trailers = Collections.synchronizedList(new ArrayList<>()); private final BasicFuture>> responseFuture; private volatile BasicFuture responseCompletion; private volatile HttpResponse informationResponse; private volatile EntityDetails entityDetails; /** * Creates a {@code ReactiveResponseConsumer}. */ public ReactiveResponseConsumer() { this.responseFuture = new BasicFuture<>(null); } /** * Creates a {@code ReactiveResponseConsumer} that will call back the supplied {@link FutureCallback} with a * streamable response. * * @param responseCallback the callback to invoke when the response is available for consumption. */ public ReactiveResponseConsumer(final FutureCallback>> responseCallback) { this.responseFuture = new BasicFuture<>(Args.notNull(responseCallback, "responseCallback")); } public Future>> getResponseFuture() { return responseFuture; } /** * Returns the intermediate (1xx) HTTP response if one was received. * * @return the information response, or {@code null} if none. */ public HttpResponse getInformationResponse() { return informationResponse; } /** * Returns the response entity details. * * @return the entity details, or {@code null} if none. */ public EntityDetails getEntityDetails() { return entityDetails; } /** * Returns the trailers received at the end of the response. * * @return a non-null list of zero or more trailers. */ public List
getTrailers() { return trailers; } @Override public void consumeResponse( final HttpResponse response, final EntityDetails entityDetails, final HttpContext httpContext, final FutureCallback resultCallback ) { this.entityDetails = entityDetails; this.responseCompletion = new BasicFuture<>(resultCallback); this.responseFuture.completed(new Message<>(response, reactiveDataConsumer)); if (entityDetails == null) { streamEnd(null); } } @Override public void informationResponse(final HttpResponse response, final HttpContext httpContext) { this.informationResponse = response; } @Override public void failed(final Exception cause) { reactiveDataConsumer.failed(cause); responseFuture.failed(cause); if (responseCompletion != null) { responseCompletion.failed(cause); } } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { reactiveDataConsumer.updateCapacity(capacityChannel); } @Override public void consume(final ByteBuffer src) throws IOException { reactiveDataConsumer.consume(src); } @Override public void streamEnd(final List trailers) { if (trailers != null) { this.trailers.addAll(trailers); } reactiveDataConsumer.streamEnd(trailers); responseCompletion.completed(null); } @Override public void releaseResources() { reactiveDataConsumer.releaseResources(); responseFuture.cancel(); if (responseCompletion != null) { responseCompletion.cancel(); } } } httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveServerExchangeHandler.java0100644 0000000 0000000 00000011551 00000250600 031715 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactive; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.reactivestreams.Publisher; /** * An implementation of {@link AsyncServerExchangeHandler} designed to work with reactive streams. * * @since 5.0 */ public final class ReactiveServerExchangeHandler implements AsyncServerExchangeHandler { private final ReactiveRequestProcessor requestProcessor; private final AtomicReference responseProducer = new AtomicReference<>(); private final ReactiveDataConsumer requestConsumer; private volatile DataStreamChannel channel; /** * Creates a {@code ReactiveServerExchangeHandler}. * * @param requestProcessor the {@link ReactiveRequestProcessor} instance to * invoke when the request is ready to be handled. */ public ReactiveServerExchangeHandler(final ReactiveRequestProcessor requestProcessor) { this.requestProcessor = requestProcessor; this.requestConsumer = new ReactiveDataConsumer(); } @Override public void handleRequest( final HttpRequest request, final EntityDetails entityDetails, final ResponseChannel responseChannel, final HttpContext context ) throws HttpException, IOException { final Callback> callback = result -> { final ReactiveDataProducer producer = new ReactiveDataProducer(result); if (channel != null) { producer.setChannel(channel); } responseProducer.set(producer); result.subscribe(producer); }; requestProcessor.processRequest(request, entityDetails, responseChannel, context, requestConsumer, callback); } @Override public void failed(final Exception cause) { requestConsumer.failed(cause); final ReactiveDataProducer p = responseProducer.get(); if (p != null) { p.onError(cause); } } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { requestConsumer.updateCapacity(capacityChannel); } @Override public void consume(final ByteBuffer src) throws IOException { requestConsumer.consume(src); } @Override public void streamEnd(final List trailers) throws HttpException, IOException { requestConsumer.streamEnd(trailers); } @Override public int available() { final ReactiveDataProducer p = responseProducer.get(); if (p == null) { return 0; } else { return p.available(); } } @Override public void produce(final DataStreamChannel channel) throws IOException { this.channel = channel; final ReactiveDataProducer p = responseProducer.get(); if (p != null) { p.produce(channel); } } @Override public void releaseResources() { final ReactiveDataProducer p = responseProducer.get(); if (p != null) { p.releaseResources(); } requestConsumer.releaseResources(); } } httpcore5-reactive/src/test/0040755 0000000 0000000 00000000000 00000250600 015051 5ustar000000000 0000000 httpcore5-reactive/src/test/java/0040755 0000000 0000000 00000000000 00000250600 015772 5ustar000000000 0000000 httpcore5-reactive/src/test/java/org/0040755 0000000 0000000 00000000000 00000250600 016561 5ustar000000000 0000000 httpcore5-reactive/src/test/java/org/apache/0040755 0000000 0000000 00000000000 00000250600 020002 5ustar000000000 0000000 httpcore5-reactive/src/test/java/org/apache/hc/0040755 0000000 0000000 00000000000 00000250600 020374 5ustar000000000 0000000 httpcore5-reactive/src/test/java/org/apache/hc/core5/0040755 0000000 0000000 00000000000 00000250600 021411 5ustar000000000 0000000 httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/0040755 0000000 0000000 00000000000 00000250600 023213 5ustar000000000 0000000 httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/BasicDataStreamChannel.java0100644 0000000 0000000 00000004632 00000250600 030340 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactive; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.nio.DataStreamChannel; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.util.ArrayList; import java.util.List; /** * Writes to a byte stream channel. */ public class BasicDataStreamChannel implements DataStreamChannel { private final WritableByteChannel byteChannel; private List
trailers; public BasicDataStreamChannel(final WritableByteChannel byteChannel) { this.byteChannel = byteChannel; } @Override public void requestOutput() { } @Override public int write(final ByteBuffer src) throws IOException { return byteChannel.write(src); } @Override public void endStream() throws IOException { if (byteChannel.isOpen()) { byteChannel.close(); } } @Override public void endStream(final List trailers) throws IOException { endStream(); if (trailers != null) { this.trailers = new ArrayList<>(); this.trailers.addAll(trailers); } } public List
getTrailers() { return trailers; } } httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveDataConsumer.java0100644 0000000 0000000 00000020113 00000250600 030760 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactive; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Notification; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.core.Single; import org.apache.hc.core5.http.HttpStreamResetException; import org.apache.hc.core5.http.nio.CapacityChannel; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; class TestReactiveDataConsumer { @Test void testStreamThatEndsNormally() throws Exception { final ReactiveDataConsumer consumer = new ReactiveDataConsumer(); final List output = Collections.synchronizedList(new ArrayList<>()); final CountDownLatch complete = new CountDownLatch(1); Observable.fromPublisher(consumer) .materialize() .forEach(byteBufferNotification -> { if (byteBufferNotification.isOnComplete()) { complete.countDown(); } else if (byteBufferNotification.isOnNext()) { output.add(byteBufferNotification.getValue()); } else { throw new IllegalArgumentException(); } }); consumer.consume(ByteBuffer.wrap(new byte[]{ '1' })); consumer.consume(ByteBuffer.wrap(new byte[]{ '2' })); consumer.consume(ByteBuffer.wrap(new byte[]{ '3' })); consumer.streamEnd(null); Assertions.assertTrue(complete.await(1, TimeUnit.SECONDS), "Stream did not finish before timeout"); Assertions.assertEquals(3, output.size()); Assertions.assertEquals(ByteBuffer.wrap(new byte[]{ '1' }), output.get(0)); Assertions.assertEquals(ByteBuffer.wrap(new byte[]{ '2' }), output.get(1)); Assertions.assertEquals(ByteBuffer.wrap(new byte[]{ '3' }), output.get(2)); } @Test void testStreamThatEndsWithError() { final ReactiveDataConsumer consumer = new ReactiveDataConsumer(); final Single>> single = Observable.fromPublisher(consumer) .materialize() .toList(); final Exception ex = new RuntimeException(); consumer.failed(ex); Assertions.assertSame(ex, single.blockingGet().get(0).getError()); } @Test void testCancellation() { final ReactiveDataConsumer consumer = new ReactiveDataConsumer(); consumer.subscribe(new Subscriber() { @Override public void onSubscribe(final Subscription s) { s.cancel(); } @Override public void onNext(final ByteBuffer byteBuffer) { } @Override public void onError(final Throwable throwable) { } @Override public void onComplete() { } }); Assertions.assertThrows(HttpStreamResetException.class, () -> consumer.consume(ByteBuffer.wrap(new byte[1024]))); } @Test void testCapacityIncrements() throws Exception { final ReactiveDataConsumer consumer = new ReactiveDataConsumer(); final ByteBuffer data = ByteBuffer.wrap(new byte[1024]); final AtomicInteger lastIncrement = new AtomicInteger(-1); final CapacityChannel channel = lastIncrement::set; consumer.updateCapacity(channel); Assertions.assertEquals(-1, lastIncrement.get(), "CapacityChannel#update should not have been invoked yet"); final AtomicInteger received = new AtomicInteger(0); final AtomicReference subscription = new AtomicReference<>(); consumer.subscribe(new Subscriber() { @Override public void onSubscribe(final Subscription s) { subscription.set(s); } @Override public void onNext(final ByteBuffer byteBuffer) { received.incrementAndGet(); } @Override public void onError(final Throwable throwable) { } @Override public void onComplete() { } }); consumer.consume(data.duplicate()); consumer.consume(data.duplicate()); consumer.consume(data.duplicate()); consumer.consume(data.duplicate()); subscription.get().request(1); Assertions.assertEquals(1024, lastIncrement.get()); subscription.get().request(2); Assertions.assertEquals(2 * 1024, lastIncrement.get()); subscription.get().request(99); Assertions.assertEquals(1024, lastIncrement.get()); } @Test void testFullResponseBuffering() throws Exception { // Due to inherent race conditions, is possible for the entire response to be buffered and completed before // the Subscriber shows up. This must be handled correctly. final ReactiveDataConsumer consumer = new ReactiveDataConsumer(); final ByteBuffer data = ByteBuffer.wrap(new byte[1024]); consumer.consume(data.duplicate()); consumer.consume(data.duplicate()); consumer.consume(data.duplicate()); consumer.streamEnd(null); Assertions.assertEquals(3L, Flowable.fromPublisher(consumer).count().blockingGet().longValue()); } @Test void testErrorBuffering() throws Exception { final ReactiveDataConsumer consumer = new ReactiveDataConsumer(); final ByteBuffer data = ByteBuffer.wrap(new byte[1024]); final RuntimeException ex = new RuntimeException(); consumer.consume(data.duplicate()); consumer.consume(data.duplicate()); consumer.consume(data.duplicate()); consumer.failed(ex); final Notification result = Flowable.fromPublisher(consumer) .materialize() .singleOrError() .blockingGet(); Assertions.assertSame(ex, result.getError()); } @Test void testFailAfterCompletion() { // Calling consumer.failed() after consumer.streamEnd() must be a no-op. // The exception must be discarded, and the subscriber must see that // the stream was successfully completed. final ReactiveDataConsumer consumer = new ReactiveDataConsumer(); consumer.streamEnd(null); final RuntimeException ex = new RuntimeException(); consumer.failed(ex); final Notification result = Flowable.fromPublisher(consumer) .materialize() .singleOrError() .blockingGet(); Assertions.assertFalse(result.isOnError()); Assertions.assertTrue(result.isOnComplete()); } } httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveDataProducer.java0100644 0000000 0000000 00000007243 00000250600 030761 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactive; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.HttpStreamResetException; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import io.reactivex.rxjava3.core.Flowable; class TestReactiveDataProducer { @Test void testStreamThatEndsNormally() throws Exception { final Flowable publisher = Flowable.just( ByteBuffer.wrap(new byte[]{ '1', '2', '3' }), ByteBuffer.wrap(new byte[]{ '4', '5', '6' })); final ReactiveDataProducer producer = new ReactiveDataProducer(publisher); final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel); producer.produce(streamChannel); Assertions.assertTrue(byteChannel.isOpen()); Assertions.assertEquals("123456", byteChannel.dump(StandardCharsets.US_ASCII)); producer.produce(streamChannel); Assertions.assertFalse(byteChannel.isOpen()); Assertions.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII)); } @Test void testStreamThatEndsWithError() throws Exception { final Flowable publisher = Flowable.concatArray( Flowable.just( ByteBuffer.wrap(new byte[]{ '1' }), ByteBuffer.wrap(new byte[]{ '2' }), ByteBuffer.wrap(new byte[]{ '3' }), ByteBuffer.wrap(new byte[]{ '4' }), ByteBuffer.wrap(new byte[]{ '5' }), ByteBuffer.wrap(new byte[]{ '6' })), Flowable.error(new RuntimeException()) ); final ReactiveDataProducer producer = new ReactiveDataProducer(publisher); final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel); producer.produce(streamChannel); Assertions.assertEquals("12345", byteChannel.dump(StandardCharsets.US_ASCII)); final HttpStreamResetException exception = Assertions.assertThrows(HttpStreamResetException.class, () -> producer.produce(streamChannel)); Assertions.assertTrue(exception.getCause() instanceof RuntimeException, "Expected published exception to be rethrown"); Assertions.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII)); } } httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveEntityProducer.java0100644 0000000 0000000 00000013062 00000250600 031360 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactive; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Collections; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpStreamResetException; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import io.reactivex.rxjava3.core.Flowable; class TestReactiveEntityProducer { private static final long CONTENT_LENGTH = 1; private static final ContentType CONTENT_TYPE = ContentType.APPLICATION_JSON; private static final String GZIP_CONTENT_ENCODING = "gzip"; @Test void testStreamThatEndsNormally() throws Exception { final Flowable publisher = Flowable.just( ByteBuffer.wrap(new byte[]{'1', '2', '3'}), ByteBuffer.wrap(new byte[]{'4', '5', '6'})); final ReactiveEntityProducer entityProducer = new ReactiveEntityProducer(publisher, CONTENT_LENGTH, CONTENT_TYPE, GZIP_CONTENT_ENCODING); final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel); entityProducer.produce(streamChannel); Assertions.assertTrue(byteChannel.isOpen(), "Should be open"); Assertions.assertEquals("123456", byteChannel.dump(StandardCharsets.US_ASCII)); entityProducer.produce(streamChannel); Assertions.assertFalse(byteChannel.isOpen(), "Should be closed"); Assertions.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII)); Assertions.assertFalse(entityProducer.isChunked()); Assertions.assertEquals(GZIP_CONTENT_ENCODING, entityProducer.getContentEncoding()); Assertions.assertEquals(Collections.emptySet(), entityProducer.getTrailerNames()); Assertions.assertEquals(CONTENT_LENGTH, entityProducer.getContentLength()); Assertions.assertEquals(CONTENT_TYPE.toString(), entityProducer.getContentType()); Assertions.assertFalse(entityProducer.isRepeatable()); Assertions.assertEquals(1, entityProducer.available()); entityProducer.releaseResources(); } @Test void testStreamThatEndsWithError() throws Exception { final Flowable publisher = Flowable.concatArray( Flowable.just( ByteBuffer.wrap(new byte[]{'1'}), ByteBuffer.wrap(new byte[]{'2'}), ByteBuffer.wrap(new byte[]{'3'}), ByteBuffer.wrap(new byte[]{'4'}), ByteBuffer.wrap(new byte[]{'5'}), ByteBuffer.wrap(new byte[]{'6'})), Flowable.error(new RuntimeException()) ); final ReactiveEntityProducer entityProducer = new ReactiveEntityProducer(publisher, CONTENT_LENGTH, CONTENT_TYPE, GZIP_CONTENT_ENCODING); final WritableByteChannelMock byteChannel = new WritableByteChannelMock(1024); final DataStreamChannel streamChannel = new BasicDataStreamChannel(byteChannel); entityProducer.produce(streamChannel); Assertions.assertEquals("12345", byteChannel.dump(StandardCharsets.US_ASCII)); final HttpStreamResetException exception = Assertions.assertThrows(HttpStreamResetException.class, () -> entityProducer.produce(streamChannel)); Assertions.assertTrue(exception.getCause() instanceof RuntimeException, "Expected published exception to be rethrown"); Assertions.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII)); entityProducer.failed(exception); Assertions.assertEquals(1, entityProducer.available()); Assertions.assertTrue(byteChannel.isOpen()); Assertions.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII)); Assertions.assertFalse(entityProducer.isChunked()); Assertions.assertEquals(GZIP_CONTENT_ENCODING, entityProducer.getContentEncoding()); Assertions.assertEquals(Collections.emptySet(), entityProducer.getTrailerNames()); Assertions.assertEquals(CONTENT_LENGTH, entityProducer.getContentLength()); Assertions.assertEquals(CONTENT_TYPE.toString(), entityProducer.getContentType()); Assertions.assertFalse(entityProducer.isRepeatable()); Assertions.assertEquals(1, entityProducer.available()); entityProducer.releaseResources(); } } httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/WritableByteChannelMock.java0100644 0000000 0000000 00000007512 00000250600 030560 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactive; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.WritableByteChannel; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; public class WritableByteChannelMock implements WritableByteChannel { private final int capacityLimit; private int capacityUsed; private ByteBuffer buf; private boolean closed; public WritableByteChannelMock(final int initialSize, final int capacityLimit) { this.buf = ByteBuffer.allocate(initialSize); this.capacityLimit = capacityLimit; } public WritableByteChannelMock(final int initialSize) { this(initialSize, 0); } private void expandCapacity(final int capacity) { final ByteBuffer oldbuffer = this.buf; this.buf = ByteBuffer.allocate(capacity); oldbuffer.flip(); this.buf.put(oldbuffer); } private void ensureCapacity(final int requiredCapacity) { if (requiredCapacity > this.buf.capacity()) { expandCapacity(requiredCapacity); } } @Override public int write(final ByteBuffer src) throws IOException { if (this.closed) { throw new ClosedChannelException(); } final int len = src.remaining(); ensureCapacity(this.buf.position() + len); if (this.capacityLimit > 0) { final int chunk = Math.min(this.capacityLimit - this.capacityUsed, len); if (chunk > 0) { final int limit = src.limit(); src.limit(src.position() + chunk); this.buf.put(src); src.limit(limit); this.capacityUsed += chunk; return chunk; } return 0; } this.buf.put(src); return len; } @Override public boolean isOpen() { return !this.closed; } @Override public void close() throws IOException { this.closed = true; } public void flush() { this.capacityUsed = 0; } public void reset() { this.capacityUsed = 0; this.buf.clear(); } public byte[] toByteArray() { final ByteBuffer dup = this.buf.duplicate(); dup.flip(); final byte[] bytes = new byte[dup.remaining()]; dup.get(bytes); return bytes; } public String dump(final Charset charset) throws CharacterCodingException { this.buf.flip(); final CharBuffer charBuffer = charset.newDecoder().decode(this.buf); this.buf.compact(); return charBuffer.toString(); } } httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/examples/0040755 0000000 0000000 00000000000 00000250600 025031 5ustar000000000 0000000 ././@LongLink0100644 0000000 0000000 00000000154 00000250600 011611 Lustar 0000000 0000000 httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/examples/ReactiveFullDuplexClientExample.javahttpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/examples/ReactiveFullDuplexClientExamp0100644 0000000 0000000 00000013765 00000250600 032666 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactive.examples; import static java.nio.charset.StandardCharsets.UTF_8; import java.net.URI; import java.nio.ByteBuffer; import java.util.Random; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.bootstrap.AsyncRequesterBootstrap; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.http.nio.AsyncRequestProducer; import org.apache.hc.core5.http.nio.support.AsyncRequestBuilder; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactive.ReactiveEntityProducer; import org.apache.hc.core5.reactive.ReactiveResponseConsumer; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.util.Timeout; import org.reactivestreams.Publisher; import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Observable; /** * Example of full-duplex HTTP/1.1 message exchanges using reactive streaming. This demo will stream randomly * generated text to the server via a POST request, while writing the response stream's events to standard output. * This demo works out-of-the-box with {@link ReactiveFullDuplexServerExample}. */ public class ReactiveFullDuplexClientExample { public static void main(final String[] args) throws Exception { String endpoint = "http://localhost:8080/echo"; if (args.length >= 1) { endpoint = args[0]; } // Create and start requester final HttpAsyncRequester requester = AsyncRequesterBootstrap.bootstrap() .setIOReactorConfig(IOReactorConfig.custom().setSoTimeout(5, TimeUnit.SECONDS).build()) .setStreamListener(new Http1StreamListener() { @Override public void onRequestHead(final HttpConnection connection, final HttpRequest request) { System.out.println(connection.getRemoteAddress() + " " + new RequestLine(request)); } @Override public void onResponseHead(final HttpConnection connection, final HttpResponse response) { System.out.println(connection.getRemoteAddress() + " " + new StatusLine(response)); } @Override public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) { if (keepAlive) { System.out.println(connection.getRemoteAddress() + " exchange completed (connection kept alive)"); } else { System.out.println(connection.getRemoteAddress() + " exchange completed (connection closed)"); } } }) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("HTTP requester shutting down"); requester.close(CloseMode.GRACEFUL); })); requester.start(); final Random random = new Random(); final Flowable publisher = Flowable.range(1, 100) .map(ignored -> { final String str = random.nextDouble() + "\n"; return ByteBuffer.wrap(str.getBytes(UTF_8)); }); final AsyncRequestProducer requestProducer = AsyncRequestBuilder.post(new URI(endpoint)) .setEntity(new ReactiveEntityProducer(publisher, -1, ContentType.TEXT_PLAIN, null)) .build(); final ReactiveResponseConsumer consumer = new ReactiveResponseConsumer(); final Future responseComplete = requester.execute(requestProducer, consumer, Timeout.ofSeconds(30), null); final Message> streamingResponse = consumer.getResponseFuture().get(); System.out.println(streamingResponse.getHead()); for (final Header header : streamingResponse.getHead().getHeaders()) { System.out.println(header); } System.out.println(); Observable.fromPublisher(streamingResponse.getBody()) .map(byteBuffer -> { final byte[] string = new byte[byteBuffer.remaining()]; byteBuffer.get(string); return new String(string); }) .materialize() .forEach(System.out::println); responseComplete.get(1, TimeUnit.MINUTES); System.out.println("Shutting down I/O reactor"); requester.initiateShutdown(); } } ././@LongLink0100644 0000000 0000000 00000000154 00000250600 011611 Lustar 0000000 0000000 httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/examples/ReactiveFullDuplexServerExample.javahttpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/examples/ReactiveFullDuplexServerExamp0100644 0000000 0000000 00000013006 00000250600 032702 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.reactive.examples; import java.net.InetSocketAddress; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.BasicEntityDetails; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactive.ReactiveServerExchangeHandler; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.util.TimeValue; /** * Example of full-duplex HTTP/1.1 message exchanges using reactive streaming. This demo server works out-of-the-box * with {@link ReactiveFullDuplexClientExample}; it can also be invoked interactively using telnet. */ public class ReactiveFullDuplexServerExample { public static void main(final String[] args) throws Exception { int port = 8080; if (args.length >= 1) { port = Integer.parseInt(args[0]); } final IOReactorConfig config = IOReactorConfig.custom() .setSoTimeout(15, TimeUnit.SECONDS) .setTcpNoDelay(true) .build(); final HttpAsyncServer server = AsyncServerBootstrap.bootstrap() .setExceptionCallback(e -> e.printStackTrace()) .setIOReactorConfig(config) .setStreamListener(new Http1StreamListener() { @Override public void onRequestHead(final HttpConnection connection, final HttpRequest request) { System.out.println(connection.getRemoteAddress() + " " + new RequestLine(request)); } @Override public void onResponseHead(final HttpConnection connection, final HttpResponse response) { System.out.println(connection.getRemoteAddress() + " " + new StatusLine(response)); } @Override public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) { if (keepAlive) { System.out.println(connection.getRemoteAddress() + " exchange completed (connection kept alive)"); } else { System.out.println(connection.getRemoteAddress() + " exchange completed (connection closed)"); } } }) .register("/echo", () -> new ReactiveServerExchangeHandler((request, entityDetails, responseChannel, context, requestBody, responseBodyFuture) -> { if (new BasicHeader(HttpHeaders.EXPECT, HeaderElements.CONTINUE).equals(request.getHeader(HttpHeaders.EXPECT))) { responseChannel.sendInformation(new BasicHttpResponse(100), context); } responseChannel.sendResponse( new BasicHttpResponse(200), new BasicEntityDetails(-1, ContentType.APPLICATION_OCTET_STREAM), context); // Simply using the request publisher as the response publisher will // cause the server to echo the request body. responseBodyFuture.execute(requestBody); })) .create(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("HTTP server shutting down"); server.close(CloseMode.GRACEFUL); })); server.start(); final Future future = server.listen(new InetSocketAddress(port), URIScheme.HTTP); final ListenerEndpoint listenerEndpoint = future.get(); System.out.print("Listening on " + listenerEndpoint.getAddress()); server.awaitShutdown(TimeValue.ofDays(Long.MAX_VALUE)); } } httpcore5-testing/0040755 0000000 0000000 00000000000 00000250600 013156 5ustar000000000 0000000 httpcore5-testing/pom.xml0100644 0000000 0000000 00000015331 00000250600 014473 0ustar000000000 0000000 4.0.0 org.apache.httpcomponents.core5 httpcore5-parent 5.4.3 httpcore5-testing Apache HttpComponents Core Integration Tests Apache HttpComponents HTTP/2 and HTTP/1.1 core component integration tests org.apache.httpcomponents.core5.httpcore5.testing org.apache.httpcomponents.core5 httpcore5 org.apache.httpcomponents.core5 httpcore5-h2 org.apache.httpcomponents.core5 httpcore5-reactive commons-cli commons-cli 1.9.0 org.slf4j slf4j-api io.reactivex.rxjava3 rxjava ${rxjava3.version} org.apache.httpcomponents.core5 httpcore5 tests org.apache.logging.log4j log4j-slf4j-impl test org.apache.logging.log4j log4j-core test org.conscrypt conscrypt-openjdk-uber test org.junit.jupiter junit-jupiter test org.hamcrest hamcrest test org.mockito mockito-core test org.testcontainers testcontainers test org.testcontainers junit-jupiter test org.openjdk.jmh jmh-core test org.openjdk.jmh jmh-generator-annprocess test docker org.apache.maven.plugins maven-failsafe-plugin ${hc.surefire.version} integration-test verify benchmark true org.apache org.codehaus.mojo exec-maven-plugin benchmark test exec test java -classpath org.openjdk.jmh.Main -rf json -rff target/jmh-result.${benchmark}.json ${benchmark} maven-project-info-reports-plugin false index dependencies dependency-info summary httpcore5-testing/src/0040755 0000000 0000000 00000000000 00000250600 013745 5ustar000000000 0000000 httpcore5-testing/src/main/0040755 0000000 0000000 00000000000 00000250600 014671 5ustar000000000 0000000 httpcore5-testing/src/main/java/0040755 0000000 0000000 00000000000 00000250600 015612 5ustar000000000 0000000 httpcore5-testing/src/main/java/org/0040755 0000000 0000000 00000000000 00000250600 016401 5ustar000000000 0000000 httpcore5-testing/src/main/java/org/apache/0040755 0000000 0000000 00000000000 00000250600 017622 5ustar000000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/0040755 0000000 0000000 00000000000 00000250600 020214 5ustar000000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/core5/0040755 0000000 0000000 00000000000 00000250600 021231 5ustar000000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/core5/benchmark/0040755 0000000 0000000 00000000000 00000250600 023163 5ustar000000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/core5/benchmark/BenchmarkConfig.java0100644 0000000 0000000 00000035147 00000250600 027055 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.benchmark; import java.io.File; import java.net.URI; import java.util.Arrays; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; public class BenchmarkConfig { private final URI uri; private final int requests; private final int concurrencyLevel; private final TimeValue timeLimit; private final Timeout socketTimeout; private final File payloadFile; private final ContentType contentType; private final int verbosity; private final boolean headInsteadOfGet; private final String[] headers; private final boolean keepAlive; private final String method; private final boolean useChunking; private final boolean useExpectContinue; private final boolean useAcceptGZip; private final String payloadText; private final String soapAction; private final boolean forceHttp2; private final boolean disableSSLVerification; private final String trustStorePath; private final String identityStorePath; private final String trustStorePassword; private final String identityStorePassword; private BenchmarkConfig(final URI uri, final int requests, final int concurrencyLevel, final TimeValue timeLimit, final Timeout socketTimeout, final File payloadFile, final ContentType contentType, final int verbosity, final boolean headInsteadOfGet, final String[] headers, final boolean keepAlive, final String method, final boolean useChunking, final boolean useExpectContinue, final boolean useAcceptGZip, final String payloadText, final String soapAction, final boolean forceHttp2, final boolean disableSSLVerification, final String trustStorePath, final String identityStorePath, final String trustStorePassword, final String identityStorePassword) { this.uri = uri; this.requests = requests; this.concurrencyLevel = concurrencyLevel; this.timeLimit = timeLimit; this.socketTimeout = socketTimeout; this.payloadFile = payloadFile; this.contentType = contentType; this.verbosity = verbosity; this.headInsteadOfGet = headInsteadOfGet; this.headers = headers; this.keepAlive = keepAlive; this.method = method; this.useChunking = useChunking; this.useExpectContinue = useExpectContinue; this.useAcceptGZip = useAcceptGZip; this.payloadText = payloadText; this.soapAction = soapAction; this.forceHttp2 = forceHttp2; this.disableSSLVerification = disableSSLVerification; this.trustStorePath = trustStorePath; this.identityStorePath = identityStorePath; this.trustStorePassword = trustStorePassword; this.identityStorePassword = identityStorePassword; } public URI getUri() { return uri; } public int getRequests() { return requests; } public int getConcurrencyLevel() { return concurrencyLevel; } public boolean isKeepAlive() { return keepAlive; } public int getVerbosity() { return verbosity; } public boolean isHeadInsteadOfGet() { return headInsteadOfGet; } public File getPayloadFile() { return payloadFile; } public ContentType getContentType() { return contentType; } public String[] getHeaders() { return headers != null ? headers.clone() : null; } public Timeout getSocketTimeout() { return socketTimeout; } public String getMethod() { return method; } public boolean isUseChunking() { return useChunking; } public boolean isUseExpectContinue() { return useExpectContinue; } public boolean isUseAcceptGZip() { return useAcceptGZip; } public String getPayloadText() { return payloadText; } public String getSoapAction() { return soapAction; } public boolean isForceHttp2() { return forceHttp2; } public boolean isDisableSSLVerification() { return disableSSLVerification; } public String getTrustStorePath() { return trustStorePath; } public String getIdentityStorePath() { return identityStorePath; } public String getTrustStorePassword() { return trustStorePassword; } public String getIdentityStorePassword() { return identityStorePassword; } public TimeValue getTimeLimit() { return timeLimit; } @Override public String toString() { return "[" + "uri=" + uri + ", requests=" + requests + ", concurrencyLevel=" + concurrencyLevel + ", timeLimit=" + timeLimit + ", socketTimeout=" + socketTimeout + ", payloadFile=" + payloadFile + ", contentType=" + contentType + ", verbosity=" + verbosity + ", headInsteadOfGet=" + headInsteadOfGet + ", headers=" + Arrays.toString(headers) + ", keepAlive=" + keepAlive + ", method='" + method + '\'' + ", useChunking=" + useChunking + ", useExpectContinue=" + useExpectContinue + ", useAcceptGZip=" + useAcceptGZip + ", payloadText='" + payloadText + '\'' + ", soapAction='" + soapAction + '\'' + ", forceHttp2=" + forceHttp2 + ", disableSSLVerification=" + disableSSLVerification + ", trustStorePath='" + trustStorePath + '\'' + ", identityStorePath='" + identityStorePath + '\'' + ", trustStorePassword='" + trustStorePassword + '\'' + ", identityStorePassword='" + identityStorePassword + '\'' + ']'; } public static BenchmarkConfig.Builder custom() { return new BenchmarkConfig.Builder(); } public static BenchmarkConfig.Builder copy(final BenchmarkConfig config) { Args.notNull(config, "Socket config"); return new Builder() .setUri(config.getUri()) .setRequests(config.getRequests()) .setConcurrencyLevel(config.getConcurrencyLevel()) .setTimeLimit(config.getTimeLimit()) .setSocketTimeout(config.getSocketTimeout()) .setPayloadFile(config.getPayloadFile()) .setContentType(config.getContentType()) .setVerbosity(config.getVerbosity()) .setHeadInsteadOfGet(config.isHeadInsteadOfGet()) .setHeaders(config.getHeaders()) .setKeepAlive(config.isKeepAlive()) .setMethod(config.getMethod()) .setUseChunking(config.isUseChunking()) .setUseExpectContinue(config.isUseExpectContinue()) .setUseAcceptGZip(config.isUseAcceptGZip()) .setPayloadText(config.getPayloadText()) .setSoapAction(config.getSoapAction()) .setForceHttp2(config.isForceHttp2()) .setDisableSSLVerification(config.isDisableSSLVerification()) .setTrustStorePath(config.getTrustStorePath()) .setIdentityStorePath(config.getIdentityStorePath()) .setTrustStorePassword(config.getTrustStorePassword()) .setIdentityStorePassword(config.getIdentityStorePassword()); } public static class Builder { private URI uri; private int requests; private int concurrencyLevel; private TimeValue timeLimit; private Timeout socketTimeout; private File payloadFile; private ContentType contentType; private int verbosity; private boolean headInsteadOfGet; private String[] headers; private boolean keepAlive; private String method; private boolean useChunking; private boolean useExpectContinue; private boolean useAcceptGZip; private String payloadText; private String soapAction; private boolean forceHttp2; private boolean disableSSLVerification; private String trustStorePath; private String identityStorePath; private String trustStorePassword; private String identityStorePassword; public Builder() { super(); this.requests = 1; this.concurrencyLevel = 1; this.keepAlive = false; this.verbosity = 0; this.headInsteadOfGet = false; this.socketTimeout = Timeout.ofSeconds(60); } public Builder setUri(final URI uri) { this.uri = uri; return this; } public Builder setRequests(final int requests) { this.requests = requests; return this; } public Builder setConcurrencyLevel(final int concurrencyLevel) { this.concurrencyLevel = concurrencyLevel; return this; } public Builder setKeepAlive(final boolean keepAlive) { this.keepAlive = keepAlive; return this; } public Builder setVerbosity(final int verbosity) { this.verbosity = verbosity; return this; } public Builder setHeadInsteadOfGet(final boolean headInsteadOfGet) { this.headInsteadOfGet = headInsteadOfGet; return this; } public Builder setContentType(final ContentType contentType) { this.contentType = contentType; return this; } public Builder setHeaders(final String[] headers) { this.headers = headers; return this; } public Builder setSocketTimeout(final Timeout socketTimeout) { this.socketTimeout = socketTimeout; return this; } public Builder setMethod(final String method) { this.method = method; return this; } public Builder setUseChunking(final boolean useChunking) { this.useChunking = useChunking; return this; } public Builder setUseExpectContinue(final boolean useExpectContinue) { this.useExpectContinue = useExpectContinue; return this; } public Builder setUseAcceptGZip(final boolean useAcceptGZip) { this.useAcceptGZip = useAcceptGZip; return this; } public Builder setPayloadFile(final File payloadFile) { this.payloadFile = payloadFile; return this; } public Builder setPayloadText(final String payloadText) { this.payloadText = payloadText; return this; } public Builder setSoapAction(final String soapAction) { this.soapAction = soapAction; return this; } public Builder setTimeLimit(final TimeValue timeLimit) { this.timeLimit = timeLimit; return this; } public Builder setForceHttp2(final boolean forceHttp2) { this.forceHttp2 = forceHttp2; return this; } public Builder setDisableSSLVerification(final boolean disableSSLVerification) { this.disableSSLVerification = disableSSLVerification; return this; } public Builder setTrustStorePath(final String trustStorePath) { this.trustStorePath = trustStorePath; return this; } public Builder setIdentityStorePath(final String identityStorePath) { this.identityStorePath = identityStorePath; return this; } public Builder setTrustStorePassword(final String trustStorePassword) { this.trustStorePassword = trustStorePassword; return this; } public Builder setIdentityStorePassword(final String identityStorePassword) { this.identityStorePassword = identityStorePassword; return this; } public BenchmarkConfig build() { return new BenchmarkConfig( uri, requests, concurrencyLevel, timeLimit, socketTimeout, payloadFile, contentType, verbosity, headInsteadOfGet, headers, keepAlive, method, useChunking, useExpectContinue, useAcceptGZip, payloadText, soapAction, forceHttp2, disableSSLVerification, trustStorePath, identityStorePath, trustStorePassword, identityStorePassword); } } } httpcore5-testing/src/main/java/org/apache/hc/core5/benchmark/BenchmarkWorker.java0100644 0000000 0000000 00000033730 00000250600 027115 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.benchmark; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.AsyncRequestProducer; import org.apache.hc.core5.http.nio.AsyncResponseConsumer; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.RequestChannel; import org.apache.hc.core5.http.nio.ResourceHolder; import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityProducer; import org.apache.hc.core5.http.nio.entity.FileEntityProducer; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; class BenchmarkWorker implements ResourceHolder { private final HttpAsyncRequester requester; private final HttpHost host; private final HttpCoreContext context; private final AtomicLong requestCount; private final CountDownLatch completionLatch; private final Stats stats; private final BenchmarkConfig config; private final AtomicReference endpointRef; public BenchmarkWorker( final HttpAsyncRequester requester, final HttpHost host, final HttpCoreContext context, final AtomicLong requestCount, final CountDownLatch completionLatch, final Stats stats, final BenchmarkConfig config) { this.requester = requester; this.host = host; this.context = context; this.requestCount = requestCount; this.completionLatch = completionLatch; this.stats = stats; this.config = config; this.endpointRef = new AtomicReference<>(); } private AsyncRequestProducer createRequestProducer() { String method = config.getMethod(); if (method == null) { method = config.isHeadInsteadOfGet() ? Method.HEAD.name() : Method.GET.name(); } final BasicHttpRequest request = new BasicHttpRequest(method, config.getUri()); final String[] headers = config.getHeaders(); if (headers != null) { for (final String s : headers) { final int pos = s.indexOf(':'); if (pos != -1) { request.addHeader(new BasicHeader(s.substring(0, pos).trim(), s.substring(pos + 1))); } } } if (!config.isKeepAlive() && !config.isForceHttp2()) { request.addHeader(new BasicHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE)); } if (config.isUseAcceptGZip()) { request.addHeader(new BasicHeader(HttpHeaders.ACCEPT_ENCODING, "gzip")); } if (config.getSoapAction() != null && config.getSoapAction().length() > 0) { request.addHeader(new BasicHeader("SOAPAction", config.getSoapAction())); } final AsyncEntityProducer entityProducer; if (config.getPayloadFile() != null) { entityProducer = new FileEntityProducer( config.getPayloadFile(), config.getContentType(), config.isUseChunking()); } else if (config.getPayloadText() != null) { entityProducer = new BasicAsyncEntityProducer( config.getPayloadText(), config.getContentType(), config.isUseChunking()); } else { entityProducer = null; } return new AsyncRequestProducer() { @Override public void sendRequest( final RequestChannel channel, final HttpContext context) throws HttpException, IOException { channel.sendRequest(request, entityProducer, context); } @Override public boolean isRepeatable() { return entityProducer == null || entityProducer.isRepeatable(); } @Override public int available() { return entityProducer != null ? entityProducer.available() : 0; } @Override public void produce(final DataStreamChannel channel) throws IOException { if (entityProducer != null) { entityProducer.produce(channel); } } @Override public void failed(final Exception cause) { if (config.getVerbosity() >= 1) { System.out.println("Failed HTTP request: " + cause.getMessage()); } } @Override public void releaseResources() { if (entityProducer != null) { entityProducer.releaseResources(); } } }; } private AsyncResponseConsumer createResponseConsumer() { return new AsyncResponseConsumer() { volatile int status; volatile Charset charset; final AtomicLong contentLength = new AtomicLong(); final AtomicReference> resultCallbackRef = new AtomicReference<>(); @Override public void consumeResponse( final HttpResponse response, final EntityDetails entityDetails, final HttpContext context, final FutureCallback resultCallback) throws HttpException, IOException { status = response.getCode(); resultCallbackRef.set(resultCallback); stats.setVersion(response.getVersion()); final Header serverHeader = response.getFirstHeader(HttpHeaders.SERVER); if (serverHeader != null) { stats.setServerName(serverHeader.getValue()); } if (config.getVerbosity() >= 2) { System.out.println(response.getCode()); } if (entityDetails != null) { if (config.getVerbosity() >= 6) { if (entityDetails.getContentType() != null) { final ContentType contentType = ContentType.parseLenient(entityDetails.getContentType()); charset = ContentType.getCharset(contentType, null); } } } else { streamEnd(null); } } @Override public void informationResponse( final HttpResponse response, final HttpContext context) throws HttpException, IOException { } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { capacityChannel.update(Integer.MAX_VALUE); } @Override public void consume(final ByteBuffer src) throws IOException { final int n = src.remaining(); contentLength.addAndGet(n); stats.incTotalContentLength(n); if (config.getVerbosity() >= 6) { final CharsetDecoder decoder = (charset != null ? charset : StandardCharsets.US_ASCII).newDecoder(); System.out.print(decoder.decode(src)); } } @Override public void streamEnd(final List trailers) throws HttpException, IOException { if (status == HttpStatus.SC_OK) { stats.incSuccessCount(); } else { stats.incFailureCount(); } stats.setContentLength(contentLength.get()); final FutureCallback resultCallback = resultCallbackRef.getAndSet(null); if (resultCallback != null) { resultCallback.completed(null); } if (config.getVerbosity() >= 6) { System.out.println(); System.out.println(); } } @Override public void failed(final Exception cause) { stats.incFailureCount(); final FutureCallback resultCallback = resultCallbackRef.getAndSet(null); if (resultCallback != null) { resultCallback.failed(cause); } if (config.getVerbosity() >= 1) { System.out.println("HTTP response error: " + cause.getMessage()); } } @Override public void releaseResources() { } }; } public void execute() { if (requestCount.decrementAndGet() >= 0) { AsyncClientEndpoint endpoint = endpointRef.get(); if (endpoint != null && !endpoint.isConnected()) { endpoint.releaseAndDiscard(); endpoint = null; } if (endpoint == null) { requester.connect(host, config.getSocketTimeout(), null, new FutureCallback() { @Override public void completed(final AsyncClientEndpoint endpoint) { endpointRef.set(endpoint); endpoint.execute( createRequestProducer(), createResponseConsumer(), context, new FutureCallback() { @Override public void completed(final Void result) { execute(); } @Override public void failed(final Exception cause) { execute(); } @Override public void cancelled() { completionLatch.countDown(); } }); } @Override public void failed(final Exception cause) { stats.incFailureCount(); if (config.getVerbosity() >= 1) { System.out.println("Connect error: " + cause.getMessage()); } execute(); } @Override public void cancelled() { completionLatch.countDown(); } }); } else { stats.incKeepAliveCount(); endpoint.execute( createRequestProducer(), createResponseConsumer(), context, new FutureCallback() { @Override public void completed(final Void result) { execute(); } @Override public void failed(final Exception cause) { execute(); } @Override public void cancelled() { completionLatch.countDown(); } }); } } else { completionLatch.countDown(); } } @Override public void releaseResources() { final AsyncClientEndpoint endpoint = endpointRef.getAndSet(null); if (endpoint != null) { endpoint.releaseAndDiscard(); } } } httpcore5-testing/src/main/java/org/apache/hc/core5/benchmark/CommandLineUtils.java0100644 0000000 0000000 00000022055 00000250600 027236 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.benchmark; import java.io.File; import java.net.URI; import java.net.URISyntaxException; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; public class CommandLineUtils { private CommandLineUtils() { // Do not allow utility class to be instantiated. } public static Options getOptions() { final Option nopt = new Option("n", true, "Number of requests to perform. " + "The default is to just perform a single request which may lead " + "to non-representative benchmarking results"); nopt.setRequired(false); nopt.setArgName("requests"); final Option copt = new Option("c", true, "Number of multiple requests to make at a time. " + "The default is to just execute a single request"); copt.setRequired(false); copt.setArgName("concurrency"); final Option topt = new Option("t", true, "Seconds to max. to spend on benchmarking"); topt.setRequired(false); topt.setArgName("time-limit"); final Option sopt = new Option("s", true, "Seconds to max. wait for each response. Default is 60 seconds"); sopt.setRequired(false); sopt.setArgName("socket-Timeout"); final Option popt = new Option("p", true, "File containing data to enclose in the request"); popt.setRequired(false); popt.setArgName("Payload file"); final Option Topt = new Option("T", true, "Content-type header to use for enclosed request data"); Topt.setRequired(false); Topt.setArgName("content-type"); final Option vopt = new Option("v", true, "Set verbosity level: " + "1 prints warnings and errors, " + "2 prints response codes, " + "3 prints message headers, " + "4 prints HTTP/2 frame info, " + "5 prints HTTP/2 flow control events, " + "6 prints response content"); vopt.setRequired(false); vopt.setArgName("verbosity"); final Option iopt = new Option("i", false, "Use HEAD instead of GET"); iopt.setRequired(false); final Option Hopt = new Option("H", true, "Add arbitrary header line, " + "eg. 'Accept-Encoding: gzip' inserted after all normal " + "header lines. (repeatable as -H \"h1: v1\",\"h2: v2\" etc)"); Hopt.setRequired(false); Hopt.setArgName("header"); final Option kopt = new Option("k", false, "Use HTTP KeepAlive feature. Default is no KeepAlive"); kopt.setRequired(false); final Option mopt = new Option("m", true, "HTTP Method. Default is GET or POST if the request to enclose data"); mopt.setRequired(false); mopt.setArgName("HTTP method"); // HttpCore specific options final Option uopt = new Option("u", false, "Chunk entity. Default is false"); uopt.setRequired(false); final Option xopt = new Option("x", false, "Use Expect-Continue. Default is false"); xopt.setRequired(false); final Option gopt = new Option("g", false, "Accept GZip. Default is false"); gopt.setRequired(false); final Option http2opt = new Option("2", false, "Force HTTP/2"); gopt.setRequired(false); final Option hopt = new Option("h", false, "Display usage information"); nopt.setRequired(false); final Options options = new Options(); options.addOption(nopt); options.addOption(copt); options.addOption(topt); options.addOption(sopt); options.addOption(popt); options.addOption(Topt); options.addOption(vopt); options.addOption(iopt); options.addOption(Hopt); options.addOption(kopt); options.addOption(mopt); // HttpCore specific options options.addOption(uopt); options.addOption(xopt); options.addOption(gopt); options.addOption(http2opt); options.addOption(hopt); return options; } public static BenchmarkConfig parseCommandLine(final CommandLine cmd) { final BenchmarkConfig.Builder builder = new BenchmarkConfig.Builder(); if (cmd.hasOption('n')) { final String s = cmd.getOptionValue('n'); try { builder.setRequests(Integer.parseInt(s)); } catch (final NumberFormatException ex) { printError("Invalid number of requests: " + s); } } if (cmd.hasOption('c')) { final String s = cmd.getOptionValue('c'); try { builder.setConcurrencyLevel(Integer.parseInt(s)); } catch (final NumberFormatException ex) { printError("Invalid number for concurrency: " + s); } } if (cmd.hasOption('t')) { final String t = cmd.getOptionValue('t'); try { builder.setTimeLimit(TimeValue.ofSeconds(Integer.parseInt(t))); } catch (final NumberFormatException ex) { printError("Invalid time limit: " + t); } } if (cmd.hasOption('s')) { final String s = cmd.getOptionValue('s'); try { builder.setSocketTimeout(Timeout.ofMilliseconds(Integer.parseInt(s))); } catch (final NumberFormatException ex) { printError("Invalid socket timeout: " + s); } } if (cmd.hasOption('p')) { final File file = new File(cmd.getOptionValue('p')); if (!file.exists()) { printError("File not found: " + file); } builder.setPayloadFile(file); } if (cmd.hasOption('T')) { builder.setContentType(ContentType.parse(cmd.getOptionValue('T'))); } if (cmd.hasOption('v')) { final String s = cmd.getOptionValue('v'); try { builder.setVerbosity(Integer.parseInt(s)); } catch (final NumberFormatException ex) { printError("Invalid verbosity level: " + s); } } if (cmd.hasOption('i')) { builder.setHeadInsteadOfGet(true); } if (cmd.hasOption('H')) { final String headerStr = cmd.getOptionValue('H'); builder.setHeaders(headerStr.split(",")); } if (cmd.hasOption('k')) { builder.setKeepAlive(true); } if (cmd.hasOption('m')) { builder.setMethod(cmd.getOptionValue('m')); } else if (cmd.hasOption('p')) { builder.setMethod(Method.POST.name()); } if (cmd.hasOption('u')) { builder.setUseChunking(true); } if (cmd.hasOption('x')) { builder.setUseExpectContinue(true); } if (cmd.hasOption('g')) { builder.setUseAcceptGZip(true); } if (cmd.hasOption('2')) { builder.setForceHttp2(true); } final String[] cmdargs = cmd.getArgs(); if (cmdargs.length > 0) { try { builder.setUri(new URI(cmdargs[0])); } catch (final URISyntaxException e) { printError("Invalid request URI: " + cmdargs[0]); } } return builder.build(); } static void showUsage(final Options options) { final HelpFormatter formatter = new HelpFormatter(); formatter.printHelp("HttpBenchmark [options] [http://]hostname[:port]/path?query", options); } static void printError(final String msg) { System.err.println(msg); showUsage(getOptions()); System.exit(-1); } } httpcore5-testing/src/main/java/org/apache/hc/core5/benchmark/HttpBenchmark.java0100644 0000000 0000000 00000050125 00000250600 026560 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.benchmark; import java.io.File; import java.io.IOException; import java.net.SocketAddress; import java.net.URI; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.nio.file.Paths; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; import javax.net.ssl.SSLContext; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Options; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.nio.ssl.BasicClientTlsStrategy; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpProcessorBuilder; import org.apache.hc.core5.http.protocol.RequestExpectContinue; import org.apache.hc.core5.http.protocol.RequestUserAgent; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.frame.FramePrinter; import org.apache.hc.core5.http2.frame.FrameType; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.impl.nio.H2StreamListener; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap; import org.apache.hc.core5.http2.protocol.H2RequestConnControl; import org.apache.hc.core5.http2.protocol.H2RequestContent; import org.apache.hc.core5.http2.protocol.H2RequestTargetHost; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.Command; import org.apache.hc.core5.reactor.IOEventHandler; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.ssl.SSLContextBuilder; import org.apache.hc.core5.ssl.SSLContexts; import org.apache.hc.core5.util.Timeout; /** * Main program of the HTTP benchmark. * * @since 4.0 */ public class HttpBenchmark { private final BenchmarkConfig config; public static void main(final String[] args) throws Exception { final Options options = CommandLineUtils.getOptions(); final CommandLineParser parser = new DefaultParser(); final CommandLine cmd = parser.parse(options, args); if (args.length == 0 || cmd.hasOption('h') || cmd.getArgs().length != 1) { CommandLineUtils.showUsage(options); System.exit(1); } final BenchmarkConfig config = CommandLineUtils.parseCommandLine(cmd); if (config.getUri() == null) { CommandLineUtils.showUsage(options); System.exit(1); } final HttpBenchmark httpBenchmark = new HttpBenchmark(config); final Results results = httpBenchmark.execute(); System.out.println(); ResultFormatter.print(System.out, results); } public HttpBenchmark(final BenchmarkConfig config) { super(); this.config = config != null ? config : BenchmarkConfig.custom().build(); } public Results execute() throws Exception { final HttpProcessorBuilder builder = HttpProcessorBuilder.create() .addAll( H2RequestContent.INSTANCE, H2RequestTargetHost.INSTANCE, H2RequestConnControl.INSTANCE, new RequestUserAgent("HttpCore-AB/5.0")); if (this.config.isUseExpectContinue()) { builder.add(RequestExpectContinue.INSTANCE); } final SSLContext sslContext; if ("https".equals(config.getUri().getScheme())) { final SSLContextBuilder sslContextBuilder = new SSLContextBuilder(); sslContextBuilder.setProtocol("SSL"); if (config.isDisableSSLVerification()) { sslContextBuilder.loadTrustMaterial(null, (chain, authType) -> true); } else if (config.getTrustStorePath() != null) { sslContextBuilder.loadTrustMaterial( new File(config.getTrustStorePath()), config.getTrustStorePassword() != null ? config.getTrustStorePassword().toCharArray() : null); } if (config.getIdentityStorePath() != null) { sslContextBuilder.loadKeyMaterial( Paths.get(config.getIdentityStorePath()), config.getIdentityStorePassword() != null ? config.getIdentityStorePassword().toCharArray() : null, config.getIdentityStorePassword() != null ? config.getIdentityStorePassword().toCharArray() : null); } sslContext = sslContextBuilder.build(); } else { sslContext = SSLContexts.createSystemDefault(); } final HttpVersionPolicy versionPolicy; if (config.isForceHttp2()) { versionPolicy = HttpVersionPolicy.FORCE_HTTP_2; } else { if (sslContext != null) { versionPolicy = HttpVersionPolicy.NEGOTIATE; } else { versionPolicy = HttpVersionPolicy.FORCE_HTTP_1; } } final Stats stats = new Stats(); try (final HttpAsyncRequester requester = H2RequesterBootstrap.bootstrap() .setHttpProcessor(builder.build()) .setTlsStrategy(new BasicClientTlsStrategy(sslContext)) .setVersionPolicy(versionPolicy) .setH2Config(H2Config.custom() .setPushEnabled(false) .build()) .setIOSessionDecorator(ioSession -> new IOSession() { @Override public String getId() { return ioSession.getId(); } @Override public Lock getLock() { return ioSession.getLock(); } @Override public void enqueue(final Command command, final Command.Priority priority) { ioSession.enqueue(command, priority); } @Override public boolean hasCommands() { return ioSession.hasCommands(); } @Override public Command poll() { return ioSession.poll(); } @Override public ByteChannel channel() { return ioSession.channel(); } @Override public SocketAddress getRemoteAddress() { return ioSession.getRemoteAddress(); } @Override public SocketAddress getLocalAddress() { return ioSession.getLocalAddress(); } @Override public int getEventMask() { return ioSession.getEventMask(); } @Override public void setEventMask(final int ops) { ioSession.setEventMask(ops); } @Override public void setEvent(final int op) { ioSession.setEvent(op); } @Override public void clearEvent(final int op) { ioSession.clearEvent(op); } @Override public void close() { ioSession.close(); } @Override public Status getStatus() { return ioSession.getStatus(); } @Override public int read(final ByteBuffer dst) throws IOException { final int bytesRead = ioSession.read(dst); if (bytesRead > 0) { stats.incTotalBytesRecv(bytesRead); } return bytesRead; } @Override public int write(final ByteBuffer src) throws IOException { final int bytesWritten = ioSession.write(src); if (bytesWritten > 0) { stats.incTotalBytesSent(bytesWritten); } return bytesWritten; } @Override public boolean isOpen() { return ioSession.isOpen(); } @Override public Timeout getSocketTimeout() { return ioSession.getSocketTimeout(); } @Override public void setSocketTimeout(final Timeout timeout) { ioSession.setSocketTimeout(timeout); } @Override public long getLastReadTime() { return ioSession.getLastReadTime(); } @Override public long getLastWriteTime() { return ioSession.getLastWriteTime(); } @Override public long getLastEventTime() { return ioSession.getLastEventTime(); } @Override public void updateReadTime() { ioSession.updateReadTime(); } @Override public void updateWriteTime() { ioSession.updateWriteTime(); } @Override public void close(final CloseMode closeMode) { ioSession.close(closeMode); } @Override public IOEventHandler getHandler() { return ioSession.getHandler(); } @Override public void upgrade(final IOEventHandler handler) { ioSession.upgrade(handler); } }) .setStreamListener(new Http1StreamListener() { @Override public void onRequestHead(final HttpConnection connection, final HttpRequest request) { if (config.getVerbosity() >= 3) { System.out.println(">> " + request.getMethod() + " " + request.getRequestUri()); final Header[] headers = request.getHeaders(); for (final Header header : headers) { System.out.println(">> " + header); } System.out.println(); } } @Override public void onResponseHead(final HttpConnection connection, final HttpResponse response) { if (config.getVerbosity() >= 3) { System.out.println("<< " + response.getCode() + " " + response.getReasonPhrase()); final Header[] headers = response.getHeaders(); for (final Header header : headers) { System.out.println("<< " + header); } System.out.println(); } } @Override public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) { } }) .setStreamListener(new H2StreamListener() { private final FramePrinter framePrinter = new FramePrinter(); @Override public void onHeaderInput( final HttpConnection connection, final int streamId, final List headers) { if (config.getVerbosity() >= 3) { for (final Header header : headers) { System.out.println("<< " + header); } System.out.println(); } } @Override public void onHeaderOutput( final HttpConnection connection, final int streamId, final List headers) { if (config.getVerbosity() >= 3) { for (final Header header : headers) { System.out.println(">> " + header); } System.out.println(); } } @Override public void onFrameInput( final HttpConnection connection, final int streamId, final RawFrame frame) { if (config.getVerbosity() >= 4) { System.out.print("<< "); try { framePrinter.printFrameInfo(frame, System.out); System.out.println(); if (!frame.isType(FrameType.DATA)) { framePrinter.printPayload(frame, System.out); System.out.println(); } } catch (final IOException ignore) { } } } @Override public void onFrameOutput( final HttpConnection connection, final int streamId, final RawFrame frame) { if (config.getVerbosity() >= 4) { System.out.print(">> "); try { framePrinter.printFrameInfo(frame, System.out); System.out.println(); if (!frame.isType(FrameType.DATA)) { framePrinter.printPayload(frame, System.out); System.out.println(); } } catch (final IOException ignore) { } } } @Override public void onInputFlowControl( final HttpConnection connection, final int streamId, final int delta, final int actualSize) { if (config.getVerbosity() >= 5) { System.out.println("<< stream " + streamId + ": " + actualSize + " " + delta); } } @Override public void onOutputFlowControl( final HttpConnection connection, final int streamId, final int delta, final int actualSize) { if (config.getVerbosity() >= 5) { System.out.println(">> stream " + streamId + ": " + actualSize + " " + delta); } } }) .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(config.getSocketTimeout()) .build()) .create()) { requester.setDefaultMaxPerRoute(config.getConcurrencyLevel()); requester.setMaxTotal(config.getConcurrencyLevel() * 2); requester.start(); return doExecute(requester, stats); } } private Results doExecute(final HttpAsyncRequester requester, final Stats stats) throws Exception { final URI requestUri = config.getUri(); final HttpHost host = new HttpHost(requestUri.getScheme(), requestUri.getHost(), requestUri.getPort()); final AtomicLong requestCount = new AtomicLong(config.getRequests()); final HttpVersion version = HttpVersion.HTTP_1_1; final CountDownLatch completionLatch = new CountDownLatch(config.getConcurrencyLevel()); final BenchmarkWorker[] workers = new BenchmarkWorker[config.getConcurrencyLevel()]; for (int i = 0; i < workers.length; i++) { final HttpCoreContext context = HttpCoreContext.create(); context.setProtocolVersion(version); final BenchmarkWorker worker = new BenchmarkWorker( requester, host, context, requestCount, completionLatch, stats, config); workers[i] = worker; } final long deadline = config.getTimeLimit() != null ? config.getTimeLimit().toMilliseconds() : Long.MAX_VALUE; final long startTime = System.currentTimeMillis(); for (int i = 0; i < workers.length; i++) { workers[i].execute(); } completionLatch.await(deadline, TimeUnit.MILLISECONDS); if (config.getVerbosity() >= 3) { System.out.println("...done"); } final long endTime = System.currentTimeMillis(); for (int i = 0; i < workers.length; i++) { workers[i].releaseResources(); } return new Results( stats.getServerName(), stats.getVersion(), host.getHostName(), host.getPort() > 0 ? host.getPort() : host.getSchemeName().equalsIgnoreCase("https") ? 443 : 80, requestUri.toASCIIString(), stats.getContentLength(), config.getConcurrencyLevel(), endTime - startTime, stats.getSuccessCount(), stats.getFailureCount(), stats.getKeepAliveCount(), stats.getTotalBytesRecv(), stats.getTotalBytesSent(), stats.getTotalContentLength()); } } httpcore5-testing/src/main/java/org/apache/hc/core5/benchmark/ResultFormatter.java0100644 0000000 0000000 00000007565 00000250600 027202 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.benchmark; import java.io.PrintStream; import java.text.NumberFormat; import java.util.Locale; public class ResultFormatter { private ResultFormatter() { // Do not allow utility class to be instantiated. } static final NumberFormat nf2 = NumberFormat.getInstance(Locale.ROOT); static final NumberFormat nf3 = NumberFormat.getInstance(Locale.ROOT); static final NumberFormat nf6 = NumberFormat.getInstance(Locale.ROOT); static { nf2.setMaximumFractionDigits(2); nf2.setMinimumFractionDigits(2); nf3.setMaximumFractionDigits(3); nf3.setMinimumFractionDigits(3); nf6.setMaximumFractionDigits(6); nf6.setMinimumFractionDigits(6); } public static void print(final PrintStream printStream, final Results results) { printStream.println("Server Software:\t\t" + results.getServerName()); printStream.println("Protocol version:\t\t" + results.getProtocolVersion()); printStream.println("Server Hostname:\t\t" + results.getHostName()); printStream.println("Server Port:\t\t\t" + results.getHostPort()); printStream.println("Document Path:\t\t\t" + results.getDocumentPath()); printStream.println("Document Length:\t\t" + results.getContentLength() + " bytes\n"); printStream.println("Concurrency Level:\t\t" + results.getConcurrencyLevel()); printStream.println("Time taken for tests:\t" + nf6.format((double) results.getTotalTimeMillis() / 1000) + " seconds"); printStream.println("Complete requests:\t\t" + results.getSuccessCount()); printStream.println("Failed requests:\t\t" + results.getFailureCount()); printStream.println("Kept alive:\t\t\t\t" + results.getKeepAliveCount()); printStream.println("Total transferred:\t\t" + results.getTotalBytesRcvd() + " bytes"); printStream.println("Content transferred:\t" + results.getTotalContentBytesRecvd() + " bytes"); printStream.println("Requests per second:\t" + nf2.format( results.getSuccessCount() / ((double) results.getTotalTimeMillis() / 1000)) + " [#/sec] (mean)"); printStream.println("Time per request:\t\t" + nf3.format( (double) results.getTotalTimeMillis() * results.getConcurrencyLevel() / results.getSuccessCount()) + " [ms] (mean)"); printStream.println("Time per request:\t\t" + nf3.format( (double) results.getTotalTimeMillis() / results.getSuccessCount()) + " [ms] (mean, across all concurrent requests)"); printStream.println("Transfer rate:\t\t\t" + nf2.format((double) results.getTotalBytesRcvd() / 1024 / ((double) results.getTotalTimeMillis() / 1000)) + " [Kbytes/sec] received"); } } httpcore5-testing/src/main/java/org/apache/hc/core5/benchmark/Results.java0100644 0000000 0000000 00000012113 00000250600 025462 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.benchmark; import org.apache.hc.core5.http.ProtocolVersion; /** * Benchmark results * * @since 4.3 */ public final class Results { private final String serverName; private final ProtocolVersion protocolVersion; private final String hostName; private final int hostPort; private final String documentPath; private final long contentLength; private final int concurrencyLevel; private final long totalTimeMillis; private final long successCount; private final long failureCount; private final long keepAliveCount; private final long totalBytesRcvd; private final long totalBytesSent; private final long totalContentBytesRecvd; public Results( final String serverName, final ProtocolVersion protocolVersion, final String hostName, final int hostPort, final String documentPath, final long contentLength, final int concurrencyLevel, final long totalTimeMillis, final long successCount, final long failureCount, final long keepAliveCount, final long totalBytesRcvd, final long totalBytesSent, final long totalContentBytesRecvd) { this.serverName = serverName; this.protocolVersion = protocolVersion; this.hostName = hostName; this.hostPort = hostPort; this.documentPath = documentPath; this.contentLength = contentLength; this.concurrencyLevel = concurrencyLevel; this.totalTimeMillis = totalTimeMillis; this.successCount = successCount; this.failureCount = failureCount; this.keepAliveCount = keepAliveCount; this.totalBytesRcvd = totalBytesRcvd; this.totalBytesSent = totalBytesSent; this.totalContentBytesRecvd = totalContentBytesRecvd; } public String getServerName() { return serverName; } public ProtocolVersion getProtocolVersion() { return protocolVersion; } public String getHostName() { return hostName; } public int getHostPort() { return hostPort; } public String getDocumentPath() { return documentPath; } public long getContentLength() { return contentLength; } public int getConcurrencyLevel() { return concurrencyLevel; } public long getTotalTimeMillis() { return totalTimeMillis; } public long getSuccessCount() { return successCount; } public long getFailureCount() { return failureCount; } public long getKeepAliveCount() { return keepAliveCount; } public long getTotalBytesRcvd() { return totalBytesRcvd; } public long getTotalBytesSent() { return totalBytesSent; } public long getTotalContentBytesRecvd() { return totalContentBytesRecvd; } @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("[serverName=").append(serverName) .append(", hostName=").append(hostName) .append(", hostPort=").append(hostPort) .append(", documentPath=").append(documentPath) .append(", contentLength=").append(contentLength) .append(", concurrencyLevel=").append(concurrencyLevel) .append(", totalTimeMillis=").append(totalTimeMillis) .append(", successCount=").append(successCount) .append(", failureCount=").append(failureCount) .append(", keepAliveCount=").append(keepAliveCount) .append(", totalBytesRcvd=").append(totalBytesRcvd) .append(", totalBytesSent=").append(totalBytesSent) .append(", totalContentBytesRecvd=").append(totalContentBytesRecvd) .append("]"); return builder.toString(); } } httpcore5-testing/src/main/java/org/apache/hc/core5/benchmark/Stats.java0100644 0000000 0000000 00000010277 00000250600 025130 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.benchmark; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.http.ProtocolVersion; /** * Statistics for an {@link HttpBenchmark HttpBenchmark}. * * @since 4.0 */ public class Stats { private final AtomicInteger successCount = new AtomicInteger(); private final AtomicInteger failureCount = new AtomicInteger(); private final AtomicInteger keepAliveCount = new AtomicInteger(); private final AtomicLong totalBytesRecv = new AtomicLong(); private final AtomicLong totalBytesSent = new AtomicLong(); private final AtomicLong contentLength = new AtomicLong(); private final AtomicLong totalContentLength = new AtomicLong(); private final AtomicReference serverNameRef = new AtomicReference<>(); private final AtomicReference versionRef = new AtomicReference<>(); public void incSuccessCount() { this.successCount.incrementAndGet(); } public int getSuccessCount() { return this.successCount.get(); } public void incFailureCount() { this.failureCount.incrementAndGet(); } public int getFailureCount() { return this.failureCount.get(); } public void incKeepAliveCount() { this.keepAliveCount.incrementAndGet(); } public int getKeepAliveCount() { return this.keepAliveCount.get(); } public void incTotalBytesRecv(final int n) { this.totalBytesRecv.addAndGet(n); } public long getTotalBytesRecv() { return this.totalBytesRecv.get(); } public void incTotalBytesSent(final int n) { this.totalBytesSent.addAndGet(n); } public long getTotalBytesSent() { return this.totalBytesSent.get(); } public void setContentLength(final long n) { this.contentLength.set(n); } public void incTotalContentLength(final long n) { this.totalContentLength.addAndGet(n); } public long getContentLength() { return this.contentLength.get(); } public long getTotalContentLength() { return this.totalContentLength.get(); } public void setServerName(final String serverName) { this.serverNameRef.set(serverName); } public String getServerName() { return this.serverNameRef.get(); } public ProtocolVersion getVersion() { return versionRef.get(); } public void setVersion(final ProtocolVersion version) { this.versionRef.set(version); } @Override public String toString() { return "Stats{" + "successCount=" + successCount + ", failureCount=" + failureCount + ", keepAliveCount=" + keepAliveCount + ", serverName=" + serverNameRef.get() + ", totalBytesRecv=" + totalBytesRecv + ", totalBytesSent=" + totalBytesSent + ", contentLength=" + contentLength + '}'; } } httpcore5-testing/src/main/java/org/apache/hc/core5/benchmark/package-info.java0100644 0000000 0000000 00000002376 00000250600 026357 0ustar000000000 0000000 /* * ==================================================================== * 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 * . * */ /** * Apache Bench (AB) clone based on HttpCore. */ package org.apache.hc.core5.benchmark; httpcore5-testing/src/main/java/org/apache/hc/core5/testing/0040755 0000000 0000000 00000000000 00000250600 022706 5ustar000000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/core5/testing/SocksProxy.java0100644 0000000 0000000 00000027344 00000250600 025704 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.net.InetAddressUtils; import org.apache.hc.core5.util.TimeValue; /** * Cheap and nasty SOCKS protocol version 5 proxy, recommended for use in unit tests only so we can test our SOCKS client code. * * @deprecated Do not use. */ @Deprecated() public class SocksProxy { private static class SocksProxyHandler { public static final int VERSION_5 = 5; public static final int COMMAND_CONNECT = 1; public static final int ATYP_DOMAINNAME = 3; private final SocksProxy parent; private final Socket socket; private volatile Socket remote; public SocksProxyHandler(final SocksProxy parent, final Socket socket) { this.parent = parent; this.socket = socket; } public void start() { new Thread(new Runnable() { @Override public void run() { try { final DataInputStream input = new DataInputStream(socket.getInputStream()); final DataOutputStream output = new DataOutputStream(socket.getOutputStream()); final Socket target = establishConnection(input, output); remote = target; final Thread t1 = pumpStream(input, target.getOutputStream()); final Thread t2 = pumpStream(target.getInputStream(), output); try { t1.join(); } catch (final InterruptedException ignored) { } try { t2.join(); } catch (final InterruptedException ignored) { } } catch (final IOException ignored) { } finally { parent.cleanupSocksProxyHandler(SocksProxyHandler.this); } } private Socket establishConnection(final DataInputStream input, final DataOutputStream output) throws IOException { final int clientVersion = input.readUnsignedByte(); if (clientVersion != VERSION_5) { throw new IOException("SOCKS implementation only supports version 5"); } final int nMethods = input.readUnsignedByte(); for (int i = 0; i < nMethods; i++) { input.readUnsignedByte(); // auth method } // response output.writeByte(VERSION_5); output.writeByte(0); // no auth method output.flush(); input.readUnsignedByte(); // client version again final int command = input.readUnsignedByte(); if (command != COMMAND_CONNECT) { throw new IOException("SOCKS implementation only supports CONNECT command"); } input.readUnsignedByte(); // reserved final String targetHost; final byte[] targetAddress; final int addressType = input.readUnsignedByte(); switch (addressType) { case InetAddressUtils.IPV4: targetHost = null; targetAddress = new byte[4]; for (int i = 0; i < targetAddress.length; i++) { targetAddress[i] = input.readByte(); } break; case InetAddressUtils.IPV6: targetHost = null; targetAddress = new byte[16]; for (int i = 0; i < targetAddress.length; i++) { targetAddress[i] = input.readByte(); } break; case ATYP_DOMAINNAME: final int length = input.readUnsignedByte(); final StringBuilder domainname = new StringBuilder(); for (int i = 0; i < length; i++) { domainname.append((char) input.readUnsignedByte()); } targetHost = domainname.toString(); targetAddress = null; break; default: throw new IOException("Unsupported address type: " + addressType); } final int targetPort = input.readUnsignedShort(); final Socket target; if (targetHost != null) { target = new Socket(targetHost, targetPort); } else { target = new Socket(InetAddress.getByAddress(targetAddress), targetPort); } output.writeByte(VERSION_5); output.writeByte(0); /* success */ output.writeByte(0); /* reserved */ final byte[] localAddress = target.getLocalAddress().getAddress(); if (localAddress.length == 4) { output.writeByte(InetAddressUtils.IPV4); } else if (localAddress.length == 16) { output.writeByte(InetAddressUtils.IPV6); } else { throw new IOException("Unsupported localAddress byte length: " + localAddress.length); } output.write(localAddress); output.writeShort(target.getLocalPort()); output.flush(); return target; } private Thread pumpStream(final InputStream input, final OutputStream output) { final Thread t = new Thread(() -> { final byte[] buffer = new byte[1024 * 8]; try { while (true) { final int read = input.read(buffer); if (read < 0) { break; } output.write(buffer, 0, read); output.flush(); } } catch (final IOException ignored) { } finally { shutdown(); } }); t.start(); return t; } }).start(); } public void shutdown() { try { this.socket.close(); } catch (final IOException ignored) { } if (this.remote != null) { try { this.remote.close(); } catch (final IOException ignored) { } } } } private final int port; private final List handlers = new ArrayList<>(); private ServerSocket server; private Thread serverThread; private final ReentrantLock lock; public SocksProxy() { this(0); } public SocksProxy(final int port) { this.port = port; this.lock = new ReentrantLock(); } public void start() throws IOException { lock.lock(); try { if (this.server == null) { this.server = new ServerSocket(this.port); this.serverThread = new Thread(() -> { try { while (true) { final Socket socket = server.accept(); startSocksProxyHandler(socket); } } catch (final IOException ignored) { } finally { if (server != null) { try { server.close(); } catch (final IOException ignored) { } server = null; } } }); this.serverThread.start(); } } finally { lock.unlock(); } } public void shutdown(final TimeValue timeout) throws InterruptedException { final long waitUntil = System.currentTimeMillis() + timeout.toMilliseconds(); Thread t = null; lock.lock(); try { if (this.server != null) { try { this.server.close(); } catch (final IOException ignored) { } finally { this.server = null; } t = this.serverThread; this.serverThread = null; } for (final SocksProxyHandler handler : this.handlers) { handler.shutdown(); } while (!this.handlers.isEmpty()) { final long waitTime = waitUntil - System.currentTimeMillis(); if (waitTime > 0) { wait(waitTime); } } } finally { lock.unlock(); } if (t != null) { final long waitTime = waitUntil - System.currentTimeMillis(); if (waitTime > 0) { t.join(waitTime); } } } protected void startSocksProxyHandler(final Socket socket) { final SocksProxyHandler handler = new SocksProxyHandler(this, socket); lock.lock(); try { this.handlers.add(handler); } finally { lock.unlock(); } handler.start(); } protected void cleanupSocksProxyHandler(final SocksProxyHandler handler) { lock.lock(); try { this.handlers.remove(handler); } finally { lock.unlock(); } } public SocketAddress getProxyAddress() { return this.server.getLocalSocketAddress(); } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/0040755 0000000 0000000 00000000000 00000250600 024327 5ustar000000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/ClassicTestClient.java0100644 0000000 0000000 00000013555 00000250600 030560 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import java.io.IOException; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLContext; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.DefaultAddressResolver; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.impl.HttpProcessors; import org.apache.hc.core5.http.impl.bootstrap.HttpRequester; import org.apache.hc.core5.http.impl.io.HttpRequestExecutor; import org.apache.hc.core5.http.io.HttpClientConnection; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.pool.PoolReusePolicy; import org.apache.hc.core5.pool.StrictConnPool; import org.apache.hc.core5.util.Asserts; import org.apache.hc.core5.util.TimeValue; public class ClassicTestClient { private final SSLContext sslContext; private final SocketConfig socketConfig; private final AtomicReference requesterRef; private HttpProcessor httpProcessor; public ClassicTestClient(final SSLContext sslContext, final SocketConfig socketConfig) { super(); this.sslContext = sslContext; this.socketConfig = socketConfig != null ? socketConfig : SocketConfig.DEFAULT; this.requesterRef = new AtomicReference<>(); } public ClassicTestClient(final SocketConfig socketConfig) { this(null, socketConfig); } public ClassicTestClient() { this(null, null); } private HttpRequester ensureRunning() { final HttpRequester requester = this.requesterRef.get(); Asserts.check(requester != null, "Requester is not running"); return requester; } private void ensureNotRunning() { final HttpRequester requester = this.requesterRef.get(); Asserts.check(requester == null, "Requester is already running"); } /** * @since 5.3 */ public void configure(final HttpProcessor httpProcessor) { ensureNotRunning(); this.httpProcessor = httpProcessor; } /** * @deprecated Use {@link #configure(HttpProcessor)}, {@link #start()}. */ @Deprecated public void start(final HttpProcessor httpProcessor) { configure(httpProcessor); start(); } public void start() { if (requesterRef.get() == null) { final HttpRequestExecutor requestExecutor = new HttpRequestExecutor( Http1Config.DEFAULT, DefaultConnectionReuseStrategy.INSTANCE, LoggingHttp1StreamListener.INSTANCE); final StrictConnPool connPool = new StrictConnPool<>( 20, 50, TimeValue.NEG_ONE_MILLISECOND, PoolReusePolicy.LIFO, LoggingConnPoolListener.INSTANCE); final HttpRequester requester = new HttpRequester( requestExecutor, httpProcessor != null ? httpProcessor : HttpProcessors.client(), connPool, socketConfig, new LoggingBHttpClientConnectionFactory(Http1Config.DEFAULT, CharCodingConfig.DEFAULT), sslContext != null ? sslContext.getSocketFactory() : null, null, null, DefaultAddressResolver.INSTANCE); requesterRef.compareAndSet(null, requester); } else { throw new IllegalStateException("Requester has already been started"); } } public void shutdown(final CloseMode closeMode) { final HttpRequester requester = requesterRef.getAndSet(null); if (requester != null) { requester.close(closeMode); } } public ClassicHttpResponse execute( final HttpHost targetHost, final ClassicHttpRequest request, final HttpContext context) throws HttpException, IOException { final HttpRequester requester = ensureRunning(); if (request.getAuthority() == null) { request.setAuthority(new URIAuthority(targetHost)); } request.setScheme(targetHost.getSchemeName()); return requester.execute(targetHost, request, socketConfig.getSoTimeout(), context); } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/ClassicTestServer.java0100644 0000000 0000000 00000017625 00000250600 030612 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import java.io.IOException; import java.net.InetAddress; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLContext; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.impl.HttpProcessors; import org.apache.hc.core5.http.impl.bootstrap.HttpServer; import org.apache.hc.core5.http.impl.io.HttpService; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.HttpServerRequestHandler; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.support.BasicHttpServerExpectationDecorator; import org.apache.hc.core5.http.io.support.BasicHttpServerRequestHandler; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http.protocol.UriPatternType; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; public class ClassicTestServer { private final SSLContext sslContext; private final SocketConfig socketConfig; private final List> routeEntries; private final AtomicReference serverRef; private Http1Config http1Config; private HttpProcessor httpProcessor; private Decorator handlerDecorator; public ClassicTestServer(final SSLContext sslContext, final SocketConfig socketConfig) { super(); this.sslContext = sslContext; this.socketConfig = socketConfig != null ? socketConfig : SocketConfig.DEFAULT; this.routeEntries = new ArrayList<>(); this.serverRef = new AtomicReference<>(); } public ClassicTestServer(final SocketConfig socketConfig) { this(null, socketConfig); } public ClassicTestServer() { this(null, null); } /** * @deprecated Use {@link #register(String, HttpRequestHandler)}. */ @Deprecated public void registerHandler(final String pattern, final HttpRequestHandler handler) { register(pattern, handler); } /** * @deprecated Use {@link #register(String, String, HttpRequestHandler)}. */ @Deprecated public void registerHandlerVirtual(final String hostname, final String pattern, final HttpRequestHandler handler) { register(hostname, pattern, handler); } private HttpServer ensureRunning() { final HttpServer server = this.serverRef.get(); Asserts.check(server != null, "Server is not running"); return server; } private void ensureNotRunning() { final HttpServer server = this.serverRef.get(); Asserts.check(server == null, "Server is already running"); } /** * @since 5.3 */ public void register(final String uriPattern, final HttpRequestHandler handler) { Args.notNull(uriPattern, "URI pattern"); Args.notNull(handler, "Request handler"); ensureNotRunning(); routeEntries.add(new RequestRouter.Entry<>(uriPattern, handler)); } /** * @since 5.3 */ public void register(final String hostname, final String uriPattern, final HttpRequestHandler handler) { Args.notNull(hostname, "Hostname"); Args.notNull(uriPattern, "URI pattern"); Args.notNull(handler, "Request handler"); ensureNotRunning(); routeEntries.add(new RequestRouter.Entry<>(hostname, uriPattern, handler)); } /** * @since 5.3 */ public void configure(final Http1Config http1Config) { ensureNotRunning(); this.http1Config = http1Config; } /** * @since 5.3 */ public void configure(final HttpProcessor httpProcessor) { ensureNotRunning(); this.httpProcessor = httpProcessor; } /** * @since 5.3 */ public void configure(final Decorator handlerDecorator) { ensureNotRunning(); this.handlerDecorator = handlerDecorator; } public int getPort() { final HttpServer server = ensureRunning(); return server.getLocalPort(); } public InetAddress getInetAddress() { final HttpServer server = ensureRunning(); return server.getInetAddress(); } /** * @deprecated Use {@link #configure(Http1Config)}, {@link #configure(HttpProcessor)}, {@link #configure(Decorator)}, {@link #start()}. */ @Deprecated public void start( final Http1Config http1Config, final HttpProcessor httpProcessor, final Decorator handlerDecorator) throws IOException { configure(http1Config); configure(httpProcessor); configure(handlerDecorator); start(); } public void start() throws IOException { if (serverRef.get() == null) { final HttpServerRequestHandler handler = new BasicHttpServerRequestHandler( RequestRouter.create(RequestRouter.LOCAL_AUTHORITY, UriPatternType.URI_PATTERN, routeEntries, RequestRouter.LOCAL_AUTHORITY_RESOLVER, null)); final HttpService httpService = new HttpService( httpProcessor != null ? httpProcessor : HttpProcessors.server(), handlerDecorator != null ? handlerDecorator.decorate(handler) : new BasicHttpServerExpectationDecorator(handler), DefaultConnectionReuseStrategy.INSTANCE, LoggingHttp1StreamListener.INSTANCE); final HttpServer server = new HttpServer( 0, httpService, InetAddress.getLoopbackAddress(), socketConfig, null, new LoggingBHttpServerConnectionFactory( sslContext != null ? URIScheme.HTTPS.id : URIScheme.HTTP.id, http1Config != null ? http1Config : Http1Config.DEFAULT, CharCodingConfig.DEFAULT), sslContext, null, LoggingExceptionListener.INSTANCE); if (serverRef.compareAndSet(null, server)) { server.start(); } } else { throw new IllegalStateException("Server already running"); } } public void shutdown(final CloseMode closeMode) { final HttpServer server = serverRef.getAndSet(null); if (server != null) { server.close(closeMode); } } } ././@LongLink0100644 0000000 0000000 00000000146 00000250600 011612 Lustar 0000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpClientConnection.javahttpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpClientConnection.jav0100644 0000000 0000000 00000012500 00000250600 032533 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import java.io.IOException; import java.net.Socket; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.util.concurrent.atomic.AtomicLong; import javax.net.ssl.SSLSocket; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.io.DefaultBHttpClientConnection; import org.apache.hc.core5.http.impl.io.SocketHolder; import org.apache.hc.core5.http.io.HttpMessageParserFactory; import org.apache.hc.core5.http.io.HttpMessageWriterFactory; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.util.Identifiable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LoggingBHttpClientConnection extends DefaultBHttpClientConnection implements Identifiable { private static final AtomicLong COUNT = new AtomicLong(); private final String id; private final Logger log; private final Logger headerLog; private final Wire wire; public LoggingBHttpClientConnection( final Http1Config http1Config, final CharsetDecoder charDecoder, final CharsetEncoder charEncoder, final ContentLengthStrategy incomingContentStrategy, final ContentLengthStrategy outgoingContentStrategy, final HttpMessageWriterFactory requestWriterFactory, final HttpMessageParserFactory responseParserFactory) { super(http1Config, charDecoder, charEncoder, incomingContentStrategy, outgoingContentStrategy, requestWriterFactory, responseParserFactory); this.id = "http-outgoing-" + COUNT.incrementAndGet(); this.log = LoggerFactory.getLogger(getClass()); this.headerLog = LoggerFactory.getLogger("org.apache.hc.core5.http.headers"); this.wire = new Wire(LoggerFactory.getLogger("org.apache.hc.core5.http.wire"), this.id); } public LoggingBHttpClientConnection(final Http1Config http1Config) { this(http1Config, null, null, null, null, null, null); } @Override public String getId() { return id; } @Override public void close() throws IOException { if (this.log.isDebugEnabled()) { this.log.debug("{}: Close connection", this.id); } super.close(); } @Override public void close(final CloseMode closeMode) { if (this.log.isDebugEnabled()) { this.log.debug("{}: Shutdown connection", this.id); } super.close(closeMode); } @Override public void bind(final Socket socket) throws IOException { super.bind(this.wire.isEnabled() ? new LoggingSocketHolder(socket, wire) : new SocketHolder(socket)); } /** * @since 5.3 */ public void bind(final SSLSocket sslSocket, final Socket baseSocket) throws IOException { super.bind(this.wire.isEnabled() ? new LoggingSocketHolder(sslSocket, baseSocket, wire) : new SocketHolder(sslSocket, baseSocket)); } @Override protected void onResponseReceived(final ClassicHttpResponse response) { if (response != null && this.headerLog.isDebugEnabled()) { this.headerLog.debug("{} << {}", this.id, new StatusLine(response)); final Header[] headers = response.getHeaders(); for (final Header header : headers) { this.headerLog.debug("{} << {}", this.id, header); } } } @Override protected void onRequestSubmitted(final ClassicHttpRequest request) { if (request != null && this.headerLog.isDebugEnabled()) { this.headerLog.debug("{} >> {}", id, new RequestLine(request)); final Header[] headers = request.getHeaders(); for (final Header header : headers) { this.headerLog.debug("{} >> {}", this.id, header); } } } } ././@LongLink0100644 0000000 0000000 00000000155 00000250600 011612 Lustar 0000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpClientConnectionFactory.javahttpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpClientConnectionFact0100644 0000000 0000000 00000011744 00000250600 032563 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import java.io.IOException; import java.net.Socket; import javax.net.ssl.SSLSocket; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.CharCodingSupport; import org.apache.hc.core5.http.io.HttpConnectionFactory; import org.apache.hc.core5.http.io.HttpMessageParserFactory; import org.apache.hc.core5.http.io.HttpMessageWriterFactory; public class LoggingBHttpClientConnectionFactory implements HttpConnectionFactory { public static final LoggingBHttpClientConnectionFactory INSTANCE = new LoggingBHttpClientConnectionFactory(); private final Http1Config http1Config; private final CharCodingConfig charCodingConfig; private final ContentLengthStrategy incomingContentStrategy; private final ContentLengthStrategy outgoingContentStrategy; private final HttpMessageWriterFactory requestWriterFactory; private final HttpMessageParserFactory responseParserFactory; public LoggingBHttpClientConnectionFactory( final Http1Config http1Config, final CharCodingConfig charCodingConfig, final ContentLengthStrategy incomingContentStrategy, final ContentLengthStrategy outgoingContentStrategy, final HttpMessageWriterFactory requestWriterFactory, final HttpMessageParserFactory responseParserFactory) { super(); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.charCodingConfig = charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT; this.incomingContentStrategy = incomingContentStrategy; this.outgoingContentStrategy = outgoingContentStrategy; this.requestWriterFactory = requestWriterFactory; this.responseParserFactory = responseParserFactory; } public LoggingBHttpClientConnectionFactory( final Http1Config http1Config, final CharCodingConfig charCodingConfig, final HttpMessageWriterFactory requestWriterFactory, final HttpMessageParserFactory responseParserFactory) { this(http1Config, charCodingConfig, null, null, requestWriterFactory, responseParserFactory); } public LoggingBHttpClientConnectionFactory( final Http1Config http1Config, final CharCodingConfig charCodingConfig) { this(http1Config, charCodingConfig, null, null, null, null); } public LoggingBHttpClientConnectionFactory() { this(null, null, null, null, null, null); } LoggingBHttpClientConnection createDetached() { return new LoggingBHttpClientConnection( http1Config, CharCodingSupport.createDecoder(charCodingConfig), CharCodingSupport.createEncoder(charCodingConfig), incomingContentStrategy, outgoingContentStrategy, requestWriterFactory, responseParserFactory); } @Override public LoggingBHttpClientConnection createConnection(final Socket socket) throws IOException { final LoggingBHttpClientConnection conn = createDetached(); conn.bind(socket); return conn; } /** * @since 5.3 */ @Override public LoggingBHttpClientConnection createConnection(final SSLSocket sslSocket, final Socket socket) throws IOException { final LoggingBHttpClientConnection conn = createDetached(); conn.bind(sslSocket, socket); return conn; } } ././@LongLink0100644 0000000 0000000 00000000146 00000250600 011612 Lustar 0000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpServerConnection.javahttpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpServerConnection.jav0100644 0000000 0000000 00000012605 00000250600 032571 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import java.io.IOException; import java.net.Socket; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.util.concurrent.atomic.AtomicLong; import javax.net.ssl.SSLSocket; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.io.DefaultBHttpServerConnection; import org.apache.hc.core5.http.impl.io.SocketHolder; import org.apache.hc.core5.http.io.HttpMessageParserFactory; import org.apache.hc.core5.http.io.HttpMessageWriterFactory; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.util.Identifiable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LoggingBHttpServerConnection extends DefaultBHttpServerConnection implements Identifiable { private static final AtomicLong COUNT = new AtomicLong(); private final String id; private final Logger log; private final Logger headerLog; private final Wire wire; public LoggingBHttpServerConnection( final String scheme, final Http1Config http1Config, final CharsetDecoder charDecoder, final CharsetEncoder charEncoder, final ContentLengthStrategy incomingContentStrategy, final ContentLengthStrategy outgoingContentStrategy, final HttpMessageParserFactory requestParserFactory, final HttpMessageWriterFactory responseWriterFactory) { super(scheme, http1Config, charDecoder, charEncoder, incomingContentStrategy, outgoingContentStrategy, requestParserFactory, responseWriterFactory); this.id = "http-incoming-" + COUNT.incrementAndGet(); this.log = LoggerFactory.getLogger(getClass()); this.headerLog = LoggerFactory.getLogger("org.apache.hc.core5.http.headers"); this.wire = new Wire(LoggerFactory.getLogger("org.apache.hc.core5.http.wire"), this.id); } @Override public String getId() { return id; } public LoggingBHttpServerConnection(final String scheme, final Http1Config http1Config) { this(scheme, http1Config, null, null, null, null, null, null); } @Override public void close() throws IOException { if (this.log.isDebugEnabled()) { this.log.debug("{}: Close connection", this.id); } super.close(); } @Override public void close(final CloseMode closeMode) { if (this.log.isDebugEnabled()) { this.log.debug("{}: Shutdown connection", this.id); } super.close(closeMode); } @Override public void bind(final Socket socket) throws IOException { super.bind(this.wire.isEnabled() ? new LoggingSocketHolder(socket, wire) : new SocketHolder(socket)); } /** * @since 5.3 */ public void bind(final SSLSocket sslSocket, final Socket baseSocket) throws IOException { super.bind(this.wire.isEnabled() ? new LoggingSocketHolder(sslSocket, baseSocket, wire) : new SocketHolder(sslSocket, baseSocket)); } @Override protected void onRequestReceived(final ClassicHttpRequest request) { if (request != null && this.headerLog.isDebugEnabled()) { this.headerLog.debug("{} >> {}", id, new RequestLine(request)); final Header[] headers = request.getHeaders(); for (final Header header : headers) { this.headerLog.debug("{} >> {}", this.id, header); } } } @Override protected void onResponseSubmitted(final ClassicHttpResponse response) { if (response != null && this.headerLog.isDebugEnabled()) { this.headerLog.debug("{} << {}", this.id, new StatusLine(response)); final Header[] headers = response.getHeaders(); for (final Header header : headers) { this.headerLog.debug("{} << {}", this.id, header); } } } } ././@LongLink0100644 0000000 0000000 00000000155 00000250600 011612 Lustar 0000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpServerConnectionFactory.javahttpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpServerConnectionFact0100644 0000000 0000000 00000012530 00000250600 032605 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import java.io.IOException; import java.net.Socket; import javax.net.ssl.SSLSocket; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.CharCodingSupport; import org.apache.hc.core5.http.io.HttpConnectionFactory; import org.apache.hc.core5.http.io.HttpMessageParserFactory; import org.apache.hc.core5.http.io.HttpMessageWriterFactory; public class LoggingBHttpServerConnectionFactory implements HttpConnectionFactory { public static final LoggingBHttpServerConnectionFactory INSTANCE = new LoggingBHttpServerConnectionFactory(); private final String scheme; private final Http1Config http1Config; private final CharCodingConfig charCodingConfig; private final ContentLengthStrategy incomingContentStrategy; private final ContentLengthStrategy outgoingContentStrategy; private final HttpMessageParserFactory requestParserFactory; private final HttpMessageWriterFactory responseWriterFactory; public LoggingBHttpServerConnectionFactory( final String scheme, final Http1Config http1Config, final CharCodingConfig charCodingConfig, final ContentLengthStrategy incomingContentStrategy, final ContentLengthStrategy outgoingContentStrategy, final HttpMessageParserFactory requestParserFactory, final HttpMessageWriterFactory responseWriterFactory) { super(); this.scheme = scheme; this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.charCodingConfig = charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT; this.incomingContentStrategy = incomingContentStrategy; this.outgoingContentStrategy = outgoingContentStrategy; this.requestParserFactory = requestParserFactory; this.responseWriterFactory = responseWriterFactory; } public LoggingBHttpServerConnectionFactory( final String scheme, final Http1Config http1Config, final CharCodingConfig charCodingConfig, final HttpMessageParserFactory requestParserFactory, final HttpMessageWriterFactory responseWriterFactory) { this(scheme, http1Config, charCodingConfig, null, null, requestParserFactory, responseWriterFactory); } public LoggingBHttpServerConnectionFactory( final String scheme, final Http1Config http1Config, final CharCodingConfig charCodingConfig) { this(scheme, http1Config, charCodingConfig, null, null, null, null); } public LoggingBHttpServerConnectionFactory() { this(null, null, null, null, null, null, null); } LoggingBHttpServerConnection createDetached(final Socket socket) { return new LoggingBHttpServerConnection( scheme != null ? scheme : (socket instanceof SSLSocket ? URIScheme.HTTPS.id : URIScheme.HTTP.id), http1Config, CharCodingSupport.createDecoder(charCodingConfig), CharCodingSupport.createEncoder(charCodingConfig), incomingContentStrategy, outgoingContentStrategy, requestParserFactory, responseWriterFactory); } @Override public LoggingBHttpServerConnection createConnection(final Socket socket) throws IOException { final LoggingBHttpServerConnection conn = createDetached(socket); conn.bind(socket); return conn; } /** * @since 5.3 */ @Override public LoggingBHttpServerConnection createConnection(final SSLSocket sslSocket, final Socket socket) throws IOException { final LoggingBHttpServerConnection conn = createDetached(socket); conn.bind(sslSocket, socket); return conn; } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingConnPoolListener.java0100644 0000000 0000000 00000005773 00000250600 031747 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.pool.ConnPoolListener; import org.apache.hc.core5.pool.ConnPoolStats; import org.apache.hc.core5.pool.PoolStats; import org.slf4j.LoggerFactory; import org.slf4j.Logger; public class LoggingConnPoolListener implements ConnPoolListener { public final static LoggingConnPoolListener INSTANCE = new LoggingConnPoolListener(); private final Logger connLog = LoggerFactory.getLogger("org.apache.hc.core5.http.connection"); private LoggingConnPoolListener() { } @Override public void onLease(final HttpHost route, final ConnPoolStats connPoolStats) { if (connLog.isDebugEnabled()) { final StringBuilder buf = new StringBuilder(); buf.append("Leased ").append(route).append(" "); final PoolStats totals = connPoolStats.getTotalStats(); buf.append(" total kept alive: ").append(totals.getAvailable()).append("; "); buf.append("total allocated: ").append(totals.getLeased() + totals.getAvailable()); buf.append(" of ").append(totals.getMax()); connLog.debug(buf.toString()); } } @Override public void onRelease(final HttpHost route, final ConnPoolStats connPoolStats) { if (connLog.isDebugEnabled()) { final StringBuilder buf = new StringBuilder(); buf.append("Released ").append(route).append(" "); final PoolStats totals = connPoolStats.getTotalStats(); buf.append(" total kept alive: ").append(totals.getAvailable()).append("; "); buf.append("total allocated: ").append(totals.getLeased() + totals.getAvailable()); buf.append(" of ").append(totals.getMax()); connLog.debug(buf.toString()); } } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingExceptionListener.java0100644 0000000 0000000 00000004444 00000250600 032150 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import java.net.SocketException; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.ExceptionListener; import org.apache.hc.core5.http.HttpConnection; import org.slf4j.LoggerFactory; import org.slf4j.Logger; public class LoggingExceptionListener implements ExceptionListener { public final static LoggingExceptionListener INSTANCE = new LoggingExceptionListener(); private final Logger connLog = LoggerFactory.getLogger("org.apache.hc.core5.http.connection"); @Override public void onError(final Exception ex) { if (ex instanceof SocketException) { connLog.debug(ex.getMessage()); } else { connLog.error(ex.getMessage(), ex); } } @Override public void onError(final HttpConnection conn, final Exception ex) { if (ex instanceof ConnectionClosedException) { connLog.debug(ex.getMessage()); } else if (ex instanceof SocketException) { connLog.debug(ex.getMessage()); } else { connLog.error(ex.getMessage(), ex); } } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingHttp1StreamListener.java0100644 0000000 0000000 00000004556 00000250600 032372 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.slf4j.LoggerFactory; import org.slf4j.Logger; public class LoggingHttp1StreamListener implements Http1StreamListener { public static final LoggingHttp1StreamListener INSTANCE = new LoggingHttp1StreamListener(); private final Logger connLog = LoggerFactory.getLogger("org.apache.hc.core5.http.connection"); @Override public void onRequestHead(final HttpConnection connection, final HttpRequest request) { } @Override public void onResponseHead(final HttpConnection connection, final HttpResponse response) { } @Override public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) { if (connLog.isDebugEnabled()) { if (keepAlive) { connLog.debug("{} connection is kept alive", LoggingSupport.getId(connection)); } else { connLog.debug("{} connection is not kept alive", LoggingSupport.getId(connection)); } } } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingInputStream.java0100644 0000000 0000000 00000005330 00000250600 030752 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import java.io.IOException; import java.io.InputStream; class LoggingInputStream extends InputStream { private final InputStream in; private final Wire wire; public LoggingInputStream(final InputStream in, final Wire wire) { super(); this.in = in; this.wire = wire; } @Override public int read() throws IOException { final int b = in.read(); if (b != -1) { wire.input(b); } return b; } @Override public int read(final byte[] b) throws IOException { final int bytesRead = in.read(b); if (bytesRead != -1) { wire.input(b, 0, bytesRead); } return bytesRead; } @Override public int read(final byte[] b, final int off, final int len) throws IOException { final int bytesRead = in.read(b, off, len); if (bytesRead != -1) { wire.input(b, off, bytesRead); } return bytesRead; } @Override public long skip(final long n) throws IOException { return super.skip(n); } @Override public int available() throws IOException { return in.available(); } @Override public void mark(final int readlimit) { super.mark(readlimit); } @Override public void reset() throws IOException { super.reset(); } @Override public boolean markSupported() { return false; } @Override public void close() throws IOException { in.close(); } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingOutputStream.java0100644 0000000 0000000 00000004124 00000250600 031153 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import java.io.IOException; import java.io.OutputStream; class LoggingOutputStream extends OutputStream { private final OutputStream out; private final Wire wire; public LoggingOutputStream(final OutputStream out, final Wire wire) { super(); this.out = out; this.wire = wire; } @Override public void write(final int b) throws IOException { wire.output(b); } @Override public void write(final byte[] b) throws IOException { wire.output(b); out.write(b); } @Override public void write(final byte[] b, final int off, final int len) throws IOException { wire.output(b, off, len); out.write(b, off, len); } @Override public void flush() throws IOException { out.flush(); } @Override public void close() throws IOException { out.close(); } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingSocketHolder.java0100644 0000000 0000000 00000004156 00000250600 031072 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import javax.net.ssl.SSLSocket; import org.apache.hc.core5.http.impl.io.SocketHolder; class LoggingSocketHolder extends SocketHolder { private final Wire wire; LoggingSocketHolder(final SSLSocket sslSocket, final Socket baseSocket, final Wire wire) { super(sslSocket, baseSocket); this.wire = wire; } LoggingSocketHolder(final Socket socket, final Wire wire) { super(socket); this.wire = wire; } @Override protected InputStream getInputStream(final Socket socket) throws IOException { return new LoggingInputStream(super.getInputStream(socket), wire); } @Override protected OutputStream getOutputStream(final Socket socket) throws IOException { return new LoggingOutputStream(super.getOutputStream(socket), wire); } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingSupport.java0100644 0000000 0000000 00000003222 00000250600 030151 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import org.apache.hc.core5.util.Identifiable; public final class LoggingSupport { public static String getId(final Object object) { if (object == null) { return null; } if (object instanceof Identifiable) { return ((Identifiable) object).getId(); } else { return object.getClass().getSimpleName() + "-" + Integer.toHexString(System.identityHashCode(object)); } } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/Wire.java0100644 0000000 0000000 00000007315 00000250600 026103 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import java.nio.ByteBuffer; import org.apache.hc.core5.http.Chars; import org.slf4j.Logger; public class Wire { private final Logger log; private final String id; public Wire(final Logger log, final String id) { super(); this.log = log; this.id = id; } private void wire(final String header, final byte[] b, final int pos, final int off) { final StringBuilder buffer = new StringBuilder(); for (int i = 0; i < off; i++) { final int ch = b[pos + i]; if (ch == Chars.CR) { buffer.append("[\\r]"); } else if (ch == Chars.LF) { buffer.append("[\\n]\""); buffer.insert(0, "\""); buffer.insert(0, header); this.log.debug("{} {}", this.id, buffer); buffer.setLength(0); } else if (ch < Chars.SP || ch >= Chars.DEL) { buffer.append("[0x"); buffer.append(Integer.toHexString(ch)); buffer.append("]"); } else { buffer.append((char) ch); } } if (buffer.length() > 0) { buffer.append('\"'); buffer.insert(0, '\"'); buffer.insert(0, header); this.log.debug("{} {}", this.id, buffer); } } public boolean isEnabled() { return this.log.isDebugEnabled(); } public void output(final byte[] b, final int pos, final int off) { wire(">> ", b, pos, off); } public void input(final byte[] b, final int pos, final int off) { wire("<< ", b, pos, off); } public void output(final byte[] b) { output(b, 0, b.length); } public void input(final byte[] b) { input(b, 0, b.length); } public void output(final int b) { output(new byte[] {(byte) b}); } public void input(final int b) { input(new byte[] {(byte) b}); } public void output(final ByteBuffer b) { if (b.hasArray()) { output(b.array(), b.arrayOffset() + b.position(), b.remaining()); } else { final byte[] tmp = new byte[b.remaining()]; b.get(tmp); output(tmp); } } public void input(final ByteBuffer b) { if (b.hasArray()) { input(b.array(), b.arrayOffset() + b.position(), b.remaining()); } else { final byte[] tmp = new byte[b.remaining()]; b.get(tmp); input(tmp); } } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/0040755 0000000 0000000 00000000000 00000250600 024703 5ustar000000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/ClassicTestClientAdapter.java0100644 0000000 0000000 00000015607 00000250600 032435 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.framework; import java.net.URI; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.testing.classic.ClassicTestClient; import org.apache.hc.core5.util.Timeout; public class ClassicTestClientAdapter extends ClientPOJOAdapter { /** * {@inheritDoc} */ @Override public Map execute(final String defaultURI, final Map request) throws Exception { // check the request for missing items. if (defaultURI == null) { throw new HttpException("defaultURL cannot be null"); } if (request == null) { throw new HttpException("request cannot be null"); } if (! request.containsKey(PATH)) { throw new HttpException("Request path should be set."); } if (! request.containsKey(METHOD)) { throw new HttpException("Request method should be set."); } final Timeout timeout; if (request.containsKey(TIMEOUT)) { timeout = Timeout.ofMilliseconds((long) request.get(TIMEOUT)); } else { timeout = null; } final ClassicTestClient client = new ClassicTestClient(SocketConfig.custom() .setSoTimeout(timeout) .build()); // Append the path to the defaultURI. String tempDefaultURI = defaultURI; if (! defaultURI.endsWith("/")) { tempDefaultURI += "/"; } final URI startingURI = new URI(tempDefaultURI + request.get(PATH)); final URI uri; // append each parameter in the query to the uri. @SuppressWarnings("unchecked") final Map queryMap = (Map) request.get(QUERY); if (queryMap != null) { final String existingQuery = startingURI.getRawQuery(); final StringBuilder newQuery = new StringBuilder(existingQuery == null ? "" : existingQuery); // append each parm to the query for (final Entry parm : queryMap.entrySet()) { newQuery.append("&").append(parm.getKey()).append("=").append(parm.getValue()); } // create a uri with the new query. uri = new URI( startingURI.getRawSchemeSpecificPart(), startingURI.getRawUserInfo(), startingURI.getHost(), startingURI.getPort(), startingURI.getRawPath(), newQuery.toString(), startingURI.getRawFragment()); } else { uri = startingURI; } final BasicClassicHttpRequest httpRequest = new BasicClassicHttpRequest(request.get(METHOD).toString(), uri); if (request.containsKey(PROTOCOL_VERSION)) { httpRequest.setVersion((ProtocolVersion) request.get(PROTOCOL_VERSION)); } // call addHeader for each header in headers. @SuppressWarnings("unchecked") final Map headersMap = (Map) request.get(HEADERS); if (headersMap != null) { for (final Entry header : headersMap.entrySet()) { httpRequest.addHeader(header.getKey(), header.getValue()); } } // call setEntity if a body is specified. final String requestBody = (String) request.get(BODY); if (requestBody != null) { final String requestContentType = (String) request.get(CONTENT_TYPE); final StringEntity entity = requestContentType != null ? new StringEntity(requestBody, ContentType.parse(requestContentType)) : new StringEntity(requestBody); httpRequest.setEntity(entity); } client.start(); // Now start the request. final HttpHost host = new HttpHost(uri.getHost(), uri.getPort()); final HttpCoreContext context = HttpCoreContext.create(); try (final ClassicHttpResponse response = client.execute(host, httpRequest, context)) { // Prepare the response. It will contain status, body, headers, and contentType. final HttpEntity entity = response.getEntity(); final String body = entity == null ? null : EntityUtils.toString(entity); final String contentType = entity == null ? null : entity.getContentType(); // prepare the returned information final Map ret = new HashMap<>(); ret.put(STATUS, response.getCode()); // convert the headers to a Map final Map headerMap = new HashMap<>(); for (final Header header : response.getHeaders()) { headerMap.put(header.getName(), header.getValue()); } ret.put(HEADERS, headerMap); ret.put(BODY, body); ret.put(CONTENT_TYPE, contentType); return ret ; } } /** * {@inheritDoc} */ @Override public String getClientName() { return "ClassicTestClient"; } } ././@LongLink0100644 0000000 0000000 00000000153 00000250600 011610 Lustar 0000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/ClassicTestClientTestingAdapter.javahttpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/ClassicTestClientTestingAdapte0100644 0000000 0000000 00000002604 00000250600 032662 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.framework; public class ClassicTestClientTestingAdapter extends ClientTestingAdapter { public ClassicTestClientTestingAdapter() { super(new ClassicTestClientAdapter()); } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/ClientPOJOAdapter.java0100644 0000000 0000000 00000013252 00000250600 030755 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.framework; import java.util.Map; /** * *

This adapter expects a request to be made up of POJOs such as Maps and Lists. In Groovy * the request could be expressed like this:

* *
 *
 * def request = [
 *                   path    : "a/path",
 *                   method  : "GET",
 *                   query   : [
 *                                parm1 : "1",
 *                                parm2 : "2",
 *                             ]
 *                   headers : [
 *                                header1 : "stuff",
 *                                header2 : "more_stuff",
 *                             ]
 *                   contentType : "application/json",
 *                   body        : '{"location" : "home" }',
 *                ]
 * 
* *

The adapter will translate this request into calls specific to a particular HTTP client.

* *

The response is then returned with POJOs with this structure:

* *
 *
 * def response = [
 *                    status      : 200,
 *                    headers     : [
 *                                      header1 : "response_stuff",
 *                                  ]
 *                    contentType : "application/json",
 *                    body        : '{"location" : "work" }',
 *                ]
 * 
* @since 5.0 */ public abstract class ClientPOJOAdapter { public static final String BODY = "body"; public static final String CONTENT_TYPE = "contentType"; public static final String HEADERS = "headers"; public static final String METHOD = "method"; public static final String NAME = "name"; public static final String PATH = "path"; public static final String PROTOCOL_VERSION = "protocolVersion"; public static final String QUERY = "query"; public static final String REQUEST = "request"; public static final String RESPONSE = "response"; public static final String STATUS = "status"; public static final String TIMEOUT = "timeout"; /** * Name of the HTTP Client that this adapter uses. * * @return name of the HTTP Client. */ public abstract String getClientName(); /** * Execute an HTTP request. * * @param defaultURI the URI used by default. The path in the request is * usually appended to it. * @param request the request as specified above. * * @return the response to the request as specified above. * * @throws Exception in case of a problem */ public abstract Map execute(String defaultURI, Map request) throws Exception; /** *

Check if a request is supported.

* *

Usually called directly by a testing framework. If an HTTP client does not support * a particular request, a non-null reason should be returned. Otherwise, if * the request is supported, return null.

* *

If this method is overridden, then the execute method should probably call * assertRequestSupported() at the beginning.

* * @param request the request as specified above. * * @return null if the request is supported; Otherwise, return a reason. */ public String checkRequestSupport(final Map request) { return null; } /** *

Assert that the request is supported

* *

Usually called by the execute method. Throws an exception if the request * is not supported.

* * @param request the request as specified above. * @throws Exception if the request is not supported. */ public void assertRequestSupported(final Map request) throws Exception { final String reason = checkRequestSupport(request); if (reason != null) { throw new Exception(reason); } } /** *

Modify the request.

* *

In a testing context, a testing framework can call this method to allow * the adapter to change the request. The request is then given to a * special request handler of the in-process HttpServer which will later check * an actual HTTP request against what is expected.

* *

In a production context, this is called by the execute method (if at all).

* * @param request the request as specified above. * @return the same request or a modification of it. */ public Map modifyRequest(final Map request) { return request; } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/ClientTestingAdapter.java0100644 0000000 0000000 00000015174 00000250600 031630 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.framework; import java.util.Map; public class ClientTestingAdapter { /** * This adapter will perform the HTTP request and return the response in the * expected format. */ protected ClientPOJOAdapter adapter; /* * The following is not expected to be changed to true, but it is to highlight * where the execute method can call the requestHandler's assertNothingThrown() * method if desired. Since this adapter's execute method does not check * the response, there is no need to call it. */ protected boolean callAssertNothingThrown; public ClientTestingAdapter() { } public ClientTestingAdapter(final ClientPOJOAdapter adapter) { this.adapter = adapter; } /** * See the documentation for the same method in {@link ClientPOJOAdapter}. This * method will typically call it. However, this method also has access to the * test's response expectations if that is needed for some reason. Furthermore, * this method also has access to the {@link TestingFrameworkRequestHandler} so * it can optionally call assertNothingThrown() before checking the response * further. It is optional because the test framework will call it later. * * @param defaultURI See execute method of {@link ClientPOJOAdapter}. * @param request See execute method of {@link ClientPOJOAdapter}. * @param requestHandler The request handler that checks the received HTTP request * with the request that was intended. If there is a * mismatch of expectations, then the requestHandler will * throw an exception. If this execute method does not want * to make further checks of the response in the case * the responseHandler threw, then the assertNothingThrown() * method should be called before doing further checks. * @param responseExpectations The response expectations of the test. * @return See return of the execute method of {@link ClientPOJOAdapter}. * @throws TestingFrameworkException in the case of a problem. */ public Map execute(final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) throws TestingFrameworkException { try { if (adapter == null) { throw new TestingFrameworkException("adapter cannot be null"); } // Call the adapter's execute method to actually make the HTTP request. final Map response = adapter.execute(defaultURI, request); /* * Adapters may call assertNothingThrown() if they would like. This would be to * make sure the following code is not executed in the event there was something * thrown in the request handler. * * Otherwise, the framework will call it when this method returns. So, it is * optional. */ if (callAssertNothingThrown) { if (requestHandler == null) { throw new TestingFrameworkException("requestHandler cannot be null"); } requestHandler.assertNothingThrown(); } return response; } catch (final TestingFrameworkException e) { throw e; } catch (final Exception ex) { throw new TestingFrameworkException(ex); } } /** * See the documentation for the same method in {@link ClientPOJOAdapter}. */ public boolean isRequestSupported(final Map request) { return adapter == null || adapter.checkRequestSupport(request) == null; } /** * See the documentation for the same method in {@link ClientPOJOAdapter}. */ public Map modifyRequest(final Map request) { return adapter == null ? request : adapter.modifyRequest(request); } /** * Generally a test's response expectations should not need to be modified, but * if a particular HTTP client (such as Groovy's RESTClient which uses HttpClient) * needs to modify the response expectations, it should do so here. After this * method returns, the {@link TestingFrameworkRequestHandler} is sent the * expectations so the request handler will return a response that matches the * expectations. When the HTTP response is obtained, the received response * is matched against the expectations. * * @param request for the format, see the documentation for {@link ClientPOJOAdapter}. * @param responseExpectations for the format, see the documentation for {@link ClientPOJOAdapter}. * @return the same or modified response expectations. */ public Map modifyResponseExpectations(final Map request, final Map responseExpectations) { return responseExpectations; } /** * Getter for the {@link ClientPOJOAdapter} that is actually used to make the * HTTP request. * * @return the {@link ClientPOJOAdapter}. */ public ClientPOJOAdapter getClientPOJOAdapter() { return adapter; } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/FrameworkTest.java0100644 0000000 0000000 00000014422 00000250600 030343 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.framework; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.BODY; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.CONTENT_TYPE; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.HEADERS; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.METHOD; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.PATH; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.PROTOCOL_VERSION; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.QUERY; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.REQUEST; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.RESPONSE; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.STATUS; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.net.URIBuilder; public class FrameworkTest { private Map request = new HashMap<>(); private Map response = new HashMap<>(); /** * Constructs a test with default values. */ public FrameworkTest() { this(null); } /** * Constructs a test with values that are passed in as well as defaults * for values that are not passed in. * * @param test Contains a REQUEST and an expected RESPONSE. * See {@link ClientPOJOAdapter} for details. */ @SuppressWarnings("unchecked") public FrameworkTest(final Map test) { if (test != null) { if (test.containsKey(REQUEST)) { request = (Map) test.get(REQUEST); } if (test.containsKey(RESPONSE)) { response = (Map) test.get(RESPONSE); } } } /** * Returns a request with defaults for any parameter that is not specified. * * @return a REQUEST map. * @throws TestingFrameworkException a problem such as an invalid URL */ public Map initRequest() throws TestingFrameworkException { // initialize to some helpful defaults final Map ret = new HashMap<>(); ret.put(PATH, TestingFramework.DEFAULT_REQUEST_PATH); ret.put(BODY, TestingFramework.DEFAULT_REQUEST_BODY); ret.put(CONTENT_TYPE, TestingFramework.DEFAULT_REQUEST_CONTENT_TYPE); ret.put(QUERY, new HashMap<>(TestingFramework.DEFAULT_REQUEST_QUERY)); ret.put(HEADERS, new HashMap<>(TestingFramework.DEFAULT_REQUEST_HEADERS)); ret.put(PROTOCOL_VERSION, TestingFramework.DEFAULT_REQUEST_PROTOCOL_VERSION); // GET is the default method. if (! request.containsKey(METHOD)) { request.put(METHOD, "GET"); } ret.putAll(request); moveAnyParametersInPathToQuery(ret); return ret; } private void moveAnyParametersInPathToQuery(final Map request) throws TestingFrameworkException { try { final String path = (String) request.get(PATH); if (path != null) { final URI uri = path.startsWith("/") ? new URI("http://localhost:8080" + path) : new URI("http://localhost:8080/"); final URIBuilder uriBuilder = new URIBuilder(uri, StandardCharsets.UTF_8); final List params = uriBuilder.getQueryParams(); @SuppressWarnings("unchecked") final Map queryMap = (Map) request.get(QUERY); for (final NameValuePair param : params) { queryMap.put(param.getName(), param.getValue()); } if (! params.isEmpty()) { request.put(PATH, uri.getPath()); } } } catch (final URISyntaxException e) { throw new TestingFrameworkException(e); } } /** * Returns an expected response with defaults for any parameter that is not specified. * * @return the RESPONSE map. */ public Map initResponseExpectations() { // 200 is the default status. if (! response.containsKey(STATUS)) { response.put(STATUS, 200); } final Map responseExpectations = new HashMap<>(); // initialize to some helpful defaults responseExpectations.put(BODY, TestingFramework.DEFAULT_RESPONSE_BODY); responseExpectations.put(CONTENT_TYPE, TestingFramework.DEFAULT_RESPONSE_CONTENT_TYPE); responseExpectations.put(HEADERS, new HashMap<>(TestingFramework.DEFAULT_RESPONSE_HEADERS)); // Now override any defaults with what is requested. responseExpectations.putAll(response); return responseExpectations; } @Override public String toString() { return "request: " + request + "\nresponse: " + response; } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/TestingFramework.java0100644 0000000 0000000 00000044362 00000250600 031047 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.framework; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.BODY; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.CONTENT_TYPE; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.HEADERS; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.METHOD; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.REQUEST; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.RESPONSE; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.STATUS; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.impl.bootstrap.HttpServer; import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.io.CloseMode; public class TestingFramework { /** * Use the ALL_METHODS list to conveniently cycle through all HTTP methods. */ public static final List ALL_METHODS = Arrays.asList("HEAD", "GET", "DELETE", "POST", "PUT", "PATCH"); /** * If an {@link ClassicTestClientTestingAdapter} is unable to return a response in * the format this testing framework is needing, then it will need to check the * item in the response (such as body, status, headers, or contentType) itself and set * the returned value of the item as ALREADY_CHECKED. */ public static final Object ALREADY_CHECKED = new Object(); /** * If a test does not specify a path, this one is used. */ public static final String DEFAULT_REQUEST_PATH = "a/path"; /** * If a test does not specify a body, this one is used. */ public static final String DEFAULT_REQUEST_BODY = "{\"location\":\"home\"}"; /** * If a test does not specify a request contentType, this one is used. */ public static final String DEFAULT_REQUEST_CONTENT_TYPE = "application/json"; /** * If a test does not specify query parameters, these are used. */ public static final Map DEFAULT_REQUEST_QUERY; /** * If a test does not specify a request headers, these are used. */ public static final Map DEFAULT_REQUEST_HEADERS; /** * If a test does not specify a protocol version, this one is used. */ public static final ProtocolVersion DEFAULT_REQUEST_PROTOCOL_VERSION = HttpVersion.HTTP_1_1; /** * If a test does not specify an expected response status, this one is used. */ public static final int DEFAULT_RESPONSE_STATUS = 200; /** * If a test does not specify an expected response body, this one is used. */ public static final String DEFAULT_RESPONSE_BODY = "{\"location\":\"work\"}"; /** * If a test does not specify an expected response contentType, this one is used. */ public static final String DEFAULT_RESPONSE_CONTENT_TYPE = "application/json"; /** * If a test does not specify expected response headers, these are used. */ public static final Map DEFAULT_RESPONSE_HEADERS; static { final Map request = new HashMap<>(); request.put("p1", "this"); request.put("p2", "that"); DEFAULT_REQUEST_QUERY = Collections.unmodifiableMap(request); Map headers = new HashMap<>(); headers.put("header1", "stuff"); headers.put("header2", "more stuff"); DEFAULT_REQUEST_HEADERS = Collections.unmodifiableMap(headers); headers = new HashMap<>(); headers.put("header3", "header_three"); headers.put("header4", "header_four"); DEFAULT_RESPONSE_HEADERS = Collections.unmodifiableMap(headers); } private ClientTestingAdapter adapter; private TestingFrameworkRequestHandler requestHandler = new TestingFrameworkRequestHandler(); private List tests = new ArrayList<>(); private HttpServer server; private int port; public TestingFramework() throws TestingFrameworkException { this(null); } public TestingFramework(final ClientTestingAdapter adapter) throws TestingFrameworkException { this.adapter = adapter; /* * By default, a set of tests that will exercise each HTTP method are pre-loaded. */ for (final String method : ALL_METHODS) { final List statusList = Arrays.asList(200, 201); for (final Integer status : statusList) { final Map request = new HashMap<>(); request.put(METHOD, method); final Map response = new HashMap<>(); response.put(STATUS, status); final Map test = new HashMap<>(); test.put(REQUEST, request); test.put(RESPONSE, response); addTest(test); } } } /** * This is not likely to be used except during the testing of this class. * It is used to inject a mocked request handler. * * @param requestHandler */ public void setRequestHandler(final TestingFrameworkRequestHandler requestHandler) { this.requestHandler = requestHandler; } /** * Run the tests that have been previously added. First, an in-process {@link HttpServer} is * started. Then, all the tests are completed by passing each test to the adapter * which will make the HTTP request. * * @throws TestingFrameworkException if there is a test failure or unexpected problem. */ public void runTests() throws TestingFrameworkException { if (adapter == null) { throw new TestingFrameworkException("adapter should not be null"); } startServer(); try { for (final FrameworkTest test : tests) { try { callAdapter(test); } catch (final Throwable t) { processThrowable(t, test); } } } finally { stopServer(); } } private void processThrowable(final Throwable t, final FrameworkTest test) throws TestingFrameworkException { final TestingFrameworkException e; if (t instanceof TestingFrameworkException) { e = (TestingFrameworkException) t; } else { e = new TestingFrameworkException(t); } e.setAdapter(adapter); e.setTest(test); throw e; } private void startServer() throws TestingFrameworkException { /* * Start an in-process server and handle all HTTP requests * with the requestHandler. */ final SocketConfig socketConfig = SocketConfig.custom() .setSoTimeout(15000, TimeUnit.MILLISECONDS) .build(); final ServerBootstrap serverBootstrap = ServerBootstrap.bootstrap() .setLocalAddress(InetAddress.getLoopbackAddress()) .setSocketConfig(socketConfig) .setRequestRouter(RequestRouter.builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", requestHandler) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()); server = serverBootstrap.create(); try { server.start(); } catch (final IOException e) { throw new TestingFrameworkException(e); } port = server.getLocalPort(); } private void stopServer() { final HttpServer local = this.server; this.server = null; if (local != null) { local.close(CloseMode.IMMEDIATE); } } private void callAdapter(final FrameworkTest test) throws TestingFrameworkException { Map request = test.initRequest(); /* * If the adapter does not support the particular request, skip the test. */ if (! adapter.isRequestSupported(request)) { return; } /* * Allow the adapter to modify the request before the request expectations * are given to the requestHandler. Typically, adapters should not have * to modify the request. */ request = adapter.modifyRequest(request); // Tell the request handler what to expect in the request. requestHandler.setRequestExpectations(request); Map responseExpectations = test.initResponseExpectations(); /* * Allow the adapter to modify the response expectations before the handler * is told what to return. Typically, adapters should not have to modify * the response expectations. */ responseExpectations = adapter.modifyResponseExpectations(request, responseExpectations); // Tell the request handler what response to return. requestHandler.setDesiredResponse(responseExpectations); /* * Use the adapter to make the HTTP call. Make sure the responseExpectations are not changed * since they have already been sent to the request handler and they will later be used * to check the response. */ final String defaultURI = getDefaultURI(); final Map response = adapter.execute( defaultURI, request, requestHandler, Collections.unmodifiableMap(responseExpectations)); /* * The adapter is welcome to call assertNothingThrown() earlier, but we will * do it here to make sure it is done. If the handler threw any exception * while checking the request it received, it will be re-thrown here. */ requestHandler.assertNothingThrown(); assertResponseMatchesExpectation(request.get(METHOD), response, responseExpectations); } @SuppressWarnings("unchecked") private void assertResponseMatchesExpectation(final Object method, final Map actualResponse, final Map expectedResponse) throws TestingFrameworkException { if (actualResponse == null) { throw new TestingFrameworkException("response should not be null"); } /* * Now check the items in the response unless the adapter says they * already checked something. */ if (actualResponse.get(STATUS) != TestingFramework.ALREADY_CHECKED) { assertStatusMatchesExpectation(actualResponse.get(STATUS), expectedResponse.get(STATUS)); } if (! method.equals("HEAD")) { if (actualResponse.get(BODY) != TestingFramework.ALREADY_CHECKED) { assertBodyMatchesExpectation(actualResponse.get(BODY), expectedResponse.get(BODY)); } if (actualResponse.get(CONTENT_TYPE) != TestingFramework.ALREADY_CHECKED) { assertContentTypeMatchesExpectation(actualResponse.get(CONTENT_TYPE), expectedResponse.get(CONTENT_TYPE)); } } if (actualResponse.get(HEADERS) != TestingFramework.ALREADY_CHECKED) { assertHeadersMatchExpectation((Map) actualResponse.get(HEADERS), (Map) expectedResponse.get(HEADERS)); } } private void assertStatusMatchesExpectation(final Object actualStatus, final Object expectedStatus) throws TestingFrameworkException { if (actualStatus == null) { throw new TestingFrameworkException("Returned status is null."); } if (expectedStatus != null && !actualStatus.equals(expectedStatus)) { throw new TestingFrameworkException("Expected status not found. expected=" + expectedStatus + "; actual=" + actualStatus); } } private void assertBodyMatchesExpectation(final Object actualBody, final Object expectedBody) throws TestingFrameworkException { if (actualBody == null) { throw new TestingFrameworkException("Returned body is null."); } if (expectedBody != null && !actualBody.equals(expectedBody)) { throw new TestingFrameworkException("Expected body not found. expected=" + expectedBody + "; actual=" + actualBody); } } private void assertContentTypeMatchesExpectation(final Object actualContentType, final Object expectedContentType) throws TestingFrameworkException { if (expectedContentType != null) { if (actualContentType == null) { throw new TestingFrameworkException("Returned contentType is null."); } if (! actualContentType.equals(expectedContentType)) { throw new TestingFrameworkException("Expected content type not found. expected=" + expectedContentType + "; actual=" + actualContentType); } } } private void assertHeadersMatchExpectation(final Map actualHeaders, final Map expectedHeaders) throws TestingFrameworkException { if (expectedHeaders == null) { return; } for (final Map.Entry expectedHeader : expectedHeaders.entrySet()) { final String expectedHeaderName = expectedHeader.getKey(); if (! actualHeaders.containsKey(expectedHeaderName)) { throw new TestingFrameworkException("Expected header not found: name=" + expectedHeaderName); } if (! actualHeaders.get(expectedHeaderName).equals(expectedHeaders.get(expectedHeaderName))) { throw new TestingFrameworkException("Header value not expected: name=" + expectedHeaderName + "; expected=" + expectedHeaders.get(expectedHeaderName) + "; actual=" + actualHeaders.get(expectedHeaderName)); } } } private String getDefaultURI() { return "http://localhost:" + port + "/"; } /** * Sets the {@link ClientTestingAdapter}. * * @param adapter */ public void setAdapter(final ClientTestingAdapter adapter) { this.adapter = adapter; } /** * Deletes all tests. */ public void deleteTests() { tests = new ArrayList<>(); } /** * Call to add a test with defaults. * * @throws TestingFrameworkException */ public void addTest() throws TestingFrameworkException { addTest(null); } /** * Call to add a test. The test is a map with a REQUEST and a RESPONSE key. * See {@link ClientPOJOAdapter} for details on the format of the request and response. * * @param test Map with a REQUEST and a RESPONSE key. * @throws TestingFrameworkException */ @SuppressWarnings("unchecked") public void addTest(final Map test) throws TestingFrameworkException { final Map testCopy = (Map) deepcopy(test); tests.add(new FrameworkTest(testCopy)); } /** * Used to make a "deep" copy of an object. This testing framework makes deep copies * of tests that are added as well as requestExpectations Maps and response Maps. * * @param orig a serializable object. * @return a deep copy of the orig object. * @throws TestingFrameworkException */ public static Object deepcopy(final Object orig) throws TestingFrameworkException { try { // this is from http://stackoverflow.com/questions/13155127/deep-copy-map-in-groovy final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(orig); oos.flush(); final ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray()); final ObjectInputStream ois = new ObjectInputStream(bin); return ois.readObject(); } catch (final ClassNotFoundException | IOException ex) { throw new TestingFrameworkException(ex); } } } ././@LongLink0100644 0000000 0000000 00000000145 00000250600 011611 Lustar 0000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/TestingFrameworkException.javahttpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/TestingFrameworkException.java0100644 0000000 0000000 00000005264 00000250600 032724 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.framework; public class TestingFrameworkException extends Exception { public static final String NO_HTTP_CLIENT = "none"; private ClientTestingAdapter adapter; private FrameworkTest test; /** * */ private static final long serialVersionUID = -1010516169283589675L; /** * Creates a WebServerTestingFrameworkException with the specified detail message. */ public TestingFrameworkException(final String message) { super(message); } public TestingFrameworkException(final Throwable cause) { super(cause); } @Override public String getMessage() { String message = super.getMessage(); if (adapter != null) { final ClientPOJOAdapter pojoAdapter = adapter.getClientPOJOAdapter(); final String tempHttpClient = pojoAdapter == null ? null : pojoAdapter.getClientName(); final String httpClient = tempHttpClient == null ? NO_HTTP_CLIENT : tempHttpClient; if (message == null) { message = "null"; } message += "\nHTTP Client=" + httpClient; } if (test != null) { if (message == null) { message = "null"; } message += "\ntest:\n" + test; } return message; } public void setAdapter(final ClientTestingAdapter adapter) { this.adapter = adapter; } public void setTest(final FrameworkTest test) { this.test = test; } } ././@LongLink0100644 0000000 0000000 00000000152 00000250600 011607 Lustar 0000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/TestingFrameworkRequestHandler.javahttpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/TestingFrameworkRequestHandler0100644 0000000 0000000 00000027557 00000250600 033005 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.framework; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.BODY; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.CONTENT_TYPE; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.HEADERS; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.METHOD; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.PROTOCOL_VERSION; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.QUERY; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.STATUS; import java.io.IOException; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.net.URIBuilder; public class TestingFrameworkRequestHandler implements HttpRequestHandler { protected Throwable thrown; protected Map requestExpectations; protected Map desiredResponse; /** * Sets the request expectations. * * @param requestExpectations the expected values of the request. * @throws TestingFrameworkException */ @SuppressWarnings("unchecked") public void setRequestExpectations(final Map requestExpectations) throws TestingFrameworkException { this.requestExpectations = (Map) TestingFramework.deepcopy(requestExpectations); } /** * Sets the desired response. The handler will return a response that matches this. * * @param desiredResponse the desired response. * @throws TestingFrameworkException */ @SuppressWarnings("unchecked") public void setDesiredResponse(final Map desiredResponse) throws TestingFrameworkException { this.desiredResponse = (Map) TestingFramework.deepcopy(desiredResponse); } /** * After the handler returns the response, any exception or failed assertion will be * in the member called "thrown". A testing framework can later call this method * which will rethrow the exception that was thrown before. * * @throws TestingFrameworkException */ public void assertNothingThrown() throws TestingFrameworkException { if (thrown != null) { final TestingFrameworkException e = thrown instanceof TestingFrameworkException ? (TestingFrameworkException) thrown : new TestingFrameworkException(thrown); thrown = null; throw e; } } /** *

Checks the HTTP request against the requestExpectations that it was previously given. * If there is a mismatch, an exception will be saved in the "thrown" member.

* *

Also, a response will be returned that matches the desiredResponse.

*/ @Override public void handle(final ClassicHttpRequest request, final ClassicHttpResponse response, final HttpContext context) throws HttpException, IOException { try { /* * Check the method against the method in the requestExpectations. */ final String actualMethod = request.getMethod(); final String expectedMethod = (String) requestExpectations.get(METHOD); if (! actualMethod.equals(expectedMethod)) { throw new TestingFrameworkException("Method not expected. " + " expected=" + expectedMethod + "; actual=" + actualMethod); } /* * Set the status to the status that is in the desiredResponse. */ final Object desiredStatus = desiredResponse.get(STATUS); if (desiredStatus != null) { response.setCode((int) desiredStatus); } /* * Check the query parameters against the parameters in requestExpectations. */ @SuppressWarnings("unchecked") final Map expectedQuery = (Map) requestExpectations.get(QUERY); if (expectedQuery != null) { final URI uri = request.getUri(); final URIBuilder uriBuilder = new URIBuilder(uri, StandardCharsets.UTF_8); final List actualParams = uriBuilder.getQueryParams(); final Map actualParamsMap = new HashMap<>(); for (final NameValuePair actualParam : actualParams) { actualParamsMap.put(actualParam.getName(), actualParam.getValue()); } for (final Map.Entry expectedParam : expectedQuery.entrySet()) { final String key = expectedParam.getKey(); if (! actualParamsMap.containsKey(key)) { throw new TestingFrameworkException("Expected parameter not found: " + key); } final String actualParamValue = actualParamsMap.get(key); final String expectedParamValue = expectedParam.getValue(); if (! actualParamValue.equals(expectedParamValue)) { throw new TestingFrameworkException("Expected parameter value not found. " + " Parameter=" + key + "; expected=" + expectedParamValue + "; actual=" + actualParamValue); } } } /* * Check the headers against the headers in requestExpectations. */ @SuppressWarnings("unchecked") final Map expectedHeaders = (Map) requestExpectations.get(HEADERS); if (expectedHeaders != null) { final Map actualHeadersMap = new HashMap<>(); final Header[] actualHeaders = request.getHeaders(); for (final Header header : actualHeaders) { actualHeadersMap.put(header.getName(), header.getValue()); } for (final Entry expectedHeader : expectedHeaders.entrySet()) { final String key = expectedHeader.getKey(); if (! actualHeadersMap.containsKey(key)) { throw new TestingFrameworkException("Expected header not found: " + key); } final String actualHeaderValue = actualHeadersMap.get(key); final String expectedHeaderValue = expectedHeader.getValue(); if (! actualHeaderValue.equals(expectedHeaderValue)) { throw new TestingFrameworkException("Expected header value not found. " + " Name=" + key + "; expected=" + expectedHeaderValue + "; actual=" + actualHeaderValue); } } } /* * Check the body. */ final String expectedBody = (String) requestExpectations.get(BODY); if (expectedBody != null) { final HttpEntity entity = request.getEntity(); final String data = EntityUtils.toString(entity); if (! data.equals(expectedBody)) { throw new TestingFrameworkException("Expected body not found. " + " Body=" + data + "; expected=" + expectedBody); } } /* * Check the contentType of the request. */ final String requestContentType = (String) requestExpectations.get(CONTENT_TYPE); if (requestContentType != null) { final HttpEntity entity = request.getEntity(); final String contentType = entity.getContentType(); final String expectedContentType = (String) requestExpectations.get(CONTENT_TYPE); if (! contentType.equals(expectedContentType)) { throw new TestingFrameworkException("Expected request content type not found. " + " Content Type=" + contentType + "; expected=" + expectedContentType); } } /* * Check the protocolVersion. */ if (requestExpectations.containsKey(PROTOCOL_VERSION)) { final ProtocolVersion protocolVersion = request.getVersion(); final ProtocolVersion expectedProtocolVersion = (ProtocolVersion) requestExpectations.get(PROTOCOL_VERSION); if (! protocolVersion.equals(expectedProtocolVersion)) { throw new TestingFrameworkException("Expected request protocol version not found. " + " Protocol Version=" + protocolVersion + "; expected=" + expectedProtocolVersion); } } /* * Return the body in desiredResponse using the contentType in desiredResponse. */ final String desiredBody = (String) desiredResponse.get(BODY); if (desiredBody != null) { final String desiredContentType = (String) desiredResponse.get(CONTENT_TYPE); final StringEntity entity = desiredContentType != null ? new StringEntity(desiredBody, ContentType.parse(desiredContentType)) : new StringEntity(desiredBody); response.setEntity(entity); } /* * Return the headers in desiredResponse. */ @SuppressWarnings("unchecked") final Map desiredHeaders = (Map) desiredResponse.get(HEADERS); if (desiredHeaders != null) { for (final Entry entry : desiredHeaders.entrySet()) { response.setHeader(entry.getKey(), entry.getValue()); } } } catch (final Throwable t) { /* * Save the throwable to be later retrieved by a call to assertNothingThrown(). */ thrown = t; } } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/0040755 0000000 0000000 00000000000 00000250600 023473 5ustar000000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/AsyncRequester.java0100644 0000000 0000000 00000010054 00000250600 027310 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.reactor.ConnectionInitiator; import org.apache.hc.core5.reactor.DefaultConnectingIOReactor; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; public class AsyncRequester extends IOReactorExecutor implements ConnectionInitiator { public AsyncRequester(final IOReactorConfig ioReactorConfig) { super(ioReactorConfig, null); } @Override DefaultConnectingIOReactor createIOReactor( final IOEventHandlerFactory ioEventHandlerFactory, final IOReactorConfig ioReactorConfig, final ThreadFactory threadFactory, final Callback sessionShutdownCallback) throws IOException { return new DefaultConnectingIOReactor( ioEventHandlerFactory, ioReactorConfig, threadFactory, LoggingIOSessionDecorator.INSTANCE, LoggingExceptionCallback.INSTANCE, LoggingIOSessionListener.INSTANCE, LoggingReactorMetricsListener.INSTANCE, sessionShutdownCallback, null); } private InetSocketAddress toSocketAddress(final HttpHost host) { int port = host.getPort(); if (port < 0) { final String scheme = host.getSchemeName(); if (URIScheme.HTTP.same(scheme)) { port = 80; } else if (URIScheme.HTTPS.same(scheme)) { port = 443; } } final String hostName = host.getHostName(); return new InetSocketAddress(hostName, port); } public Future requestSession(final HttpHost host, final Timeout timeout, final FutureCallback callback) { Args.notNull(host, "Host"); return reactor().connect(host, toSocketAddress(host), null, timeout, null, callback); } @Override public Future connect( final NamedEndpoint remoteEndpoint, final SocketAddress remoteAddress, final SocketAddress localAddress, final Timeout timeout, final Object attachment, final FutureCallback callback) { return reactor().connect(remoteEndpoint, remoteAddress, localAddress, timeout, attachment, callback); } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/AsyncServer.java0100644 0000000 0000000 00000005524 00000250600 026605 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.util.Set; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.reactor.DefaultListeningIOReactor; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.ListenerEndpoint; public class AsyncServer extends IOReactorExecutor { public AsyncServer(final IOReactorConfig ioReactorConfig) { super(ioReactorConfig, null); } @Override DefaultListeningIOReactor createIOReactor( final IOEventHandlerFactory ioEventHandlerFactory, final IOReactorConfig ioReactorConfig, final ThreadFactory threadFactory, final Callback sessionShutdownCallback) throws IOException { return new DefaultListeningIOReactor( ioEventHandlerFactory, ioReactorConfig, threadFactory, threadFactory, LoggingIOSessionDecorator.INSTANCE, LoggingExceptionCallback.INSTANCE, LoggingIOSessionListener.INSTANCE, LoggingReactorMetricsListener.INSTANCE, sessionShutdownCallback, null); } public Future listen(final InetSocketAddress address) { return reactor().listen(address, null); } public Set getEndpoints() { return reactor().getEndpoints(); } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/ClientSessionEndpoint.java0100644 0000000 0000000 00000013432 00000250600 030621 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.concurrent.BasicFuture; import org.apache.hc.core5.concurrent.CompletingFutureContribution; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.AsyncRequestProducer; import org.apache.hc.core5.http.nio.AsyncResponseConsumer; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.nio.command.RequestExecutionCommand; import org.apache.hc.core5.http.nio.command.ShutdownCommand; import org.apache.hc.core5.http.nio.support.BasicClientExchangeHandler; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.ModalCloseable; import org.apache.hc.core5.reactor.Command; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.util.Asserts; /** * Client endpoint that can be used to initiate HTTP message exchanges. * * @since 5.0 */ @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL) public final class ClientSessionEndpoint implements ModalCloseable { private final IOSession ioSession; private final AtomicBoolean closed; public ClientSessionEndpoint(final IOSession ioSession) { super(); this.ioSession = ioSession; this.closed = new AtomicBoolean(); } public void execute(final Command command, final Command.Priority priority) { ioSession.enqueue(command, priority); if (!ioSession.isOpen()) { command.cancel(); } } public void execute( final AsyncClientExchangeHandler exchangeHandler, final HandlerFactory pushHandlerFactory, final HttpContext context) { Asserts.check(!closed.get(), "Connection is already closed"); final Command executionCommand = new RequestExecutionCommand(exchangeHandler, pushHandlerFactory, null, context); ioSession.enqueue(executionCommand, Command.Priority.NORMAL); if (!ioSession.isOpen()) { exchangeHandler.failed(new ConnectionClosedException()); } } public void execute( final AsyncClientExchangeHandler exchangeHandler, final HttpContext context) { execute(exchangeHandler, null, context); } public Future execute( final AsyncRequestProducer requestProducer, final AsyncResponseConsumer responseConsumer, final HandlerFactory pushHandlerFactory, final HttpContext context, final FutureCallback callback) { Asserts.check(!closed.get(), "Connection is already closed"); final BasicFuture future = new BasicFuture<>(callback); execute(new BasicClientExchangeHandler<>(requestProducer, responseConsumer, new CompletingFutureContribution(future)), pushHandlerFactory, context); return future; } public Future execute( final AsyncRequestProducer requestProducer, final AsyncResponseConsumer responseConsumer, final HttpContext context, final FutureCallback callback) { return execute(requestProducer, responseConsumer, null, context, callback); } public Future execute( final AsyncRequestProducer requestProducer, final AsyncResponseConsumer responseConsumer, final FutureCallback callback) { return execute(requestProducer, responseConsumer, null, null, callback); } public boolean isOpen() { return !closed.get() && ioSession.isOpen(); } @Override public void close(final CloseMode closeMode) { if (closed.compareAndSet(false, true)) { if (closeMode == CloseMode.GRACEFUL) { ioSession.enqueue(ShutdownCommand.GRACEFUL, Command.Priority.NORMAL); } else { ioSession.close(closeMode); } } } @Override public void close() throws IOException { if (closed.compareAndSet(false, true)) { ioSession.enqueue(ShutdownCommand.GRACEFUL, Command.Priority.IMMEDIATE); } } @Override public String toString() { return ioSession.toString(); } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/H2TestClient.java0100644 0000000 0000000 00000015435 00000250600 026613 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.net.ssl.SSLContext; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.HttpProcessors; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http.protocol.UriPatternType; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.impl.H2Processors; import org.apache.hc.core5.http2.nio.support.DefaultAsyncPushConsumerFactory; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; import org.apache.hc.core5.util.Args; public class H2TestClient extends HttpTestClient { private final List>> routeEntries; private H2Config h2Config; private Http1Config http1Config; public H2TestClient( final IOReactorConfig ioReactorConfig, final SSLContext sslContext, final SSLSessionInitializer sslSessionInitializer, final SSLSessionVerifier sslSessionVerifier) throws IOException { super(ioReactorConfig, sslContext, sslSessionInitializer, sslSessionVerifier); this.routeEntries = new ArrayList<>(); } public H2TestClient() throws IOException { this(IOReactorConfig.DEFAULT, null, null, null); } public void register(final String uriPattern, final Supplier supplier) { Args.notNull(uriPattern, "URI pattern"); Args.notNull(supplier, "Push consumer supplier"); routeEntries.add(new RequestRouter.Entry<>(uriPattern, supplier)); } /** * @since 5.3 */ public void configure(final H2Config h2Config) { ensureNotRunning(); this.h2Config = h2Config; this.http1Config = null; } /** * @since 5.3 */ public void configure(final Http1Config http1Config) { ensureNotRunning(); this.http1Config = http1Config; this.h2Config = null; } /** * @deprecated Use {@link #startExecution(IOEventHandlerFactory)}. */ @Deprecated public void start(final IOEventHandlerFactory handlerFactory) throws IOException { super.execute(handlerFactory); } /** * @deprecated Use {@link #configure(H2Config)}, {@link #configure(HttpProcessor)}, {@link #start()}. */ @Deprecated public void start(final HttpProcessor httpProcessor, final H2Config h2Config) throws IOException { configure(h2Config); configure(httpProcessor); try { start(); } catch (final IOException | RuntimeException ex) { throw ex; } catch (final Exception ex) { throw new IOException(ex); } } /** * @deprecated Use {@link #configure(Http1Config)}, {@link #configure(HttpProcessor)}, {@link #start()}. */ @Deprecated public void start(final HttpProcessor httpProcessor, final Http1Config http1Config) throws IOException { configure(http1Config); configure(httpProcessor); try { start(); } catch (final IOException | RuntimeException ex) { throw ex; } catch (final Exception ex) { throw new IOException(ex); } } /** * @deprecated Use {@link #configure(H2Config)}, {@link #start()}. */ @Deprecated public void start(final H2Config h2Config) throws IOException { start(null, h2Config); } /** * @deprecated Use {@link #configure(Http1Config)}, {@link #start()}. */ @Deprecated public void start(final Http1Config http1Config) throws IOException { start(null, http1Config); } @Override public void start() throws Exception { if (http1Config != null) { start(new InternalClientProtocolNegotiationStarter( httpProcessor != null ? httpProcessor : HttpProcessors.client(), new DefaultAsyncPushConsumerFactory(RequestRouter.create( RequestRouter.LOCAL_AUTHORITY, UriPatternType.URI_PATTERN, routeEntries, RequestRouter.LOCAL_AUTHORITY_RESOLVER, null)), HttpVersionPolicy.FORCE_HTTP_1, H2Config.DEFAULT, http1Config, CharCodingConfig.DEFAULT, sslContext, sslSessionInitializer, sslSessionVerifier, LoggingExceptionCallback.INSTANCE)); } else { start(new InternalClientProtocolNegotiationStarter( httpProcessor != null ? httpProcessor : H2Processors.client(), new DefaultAsyncPushConsumerFactory(RequestRouter.create( RequestRouter.LOCAL_AUTHORITY, UriPatternType.URI_PATTERN, routeEntries, RequestRouter.LOCAL_AUTHORITY_RESOLVER, null)), HttpVersionPolicy.FORCE_HTTP_2, h2Config, Http1Config.DEFAULT, CharCodingConfig.DEFAULT, sslContext, sslSessionInitializer, sslSessionVerifier, LoggingExceptionCallback.INSTANCE)); } } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/H2TestServer.java0100644 0000000 0000000 00000017074 00000250600 026644 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import java.net.InetSocketAddress; import javax.net.ssl.SSLContext; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.HttpProcessors; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.support.BasicAsyncServerExpectationDecorator; import org.apache.hc.core5.http.nio.support.DefaultAsyncResponseExchangeHandlerFactory; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http.protocol.UriPatternType; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.impl.H2Processors; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; public class H2TestServer extends HttpTestServer { private H2Config h2Config; private Http1Config http1Config; public H2TestServer( final IOReactorConfig ioReactorConfig, final SSLContext sslContext, final SSLSessionInitializer sslSessionInitializer, final SSLSessionVerifier sslSessionVerifier) throws IOException { super(ioReactorConfig, sslContext, sslSessionInitializer, sslSessionVerifier); } public H2TestServer() throws IOException { this(IOReactorConfig.DEFAULT, null, null, null); } /** * @since 5.3 */ public void configure(final H2Config h2Config) { ensureNotRunning(); this.h2Config = h2Config; this.http1Config = null; } /** * @since 5.3 */ public void configure(final Http1Config http1Config) { ensureNotRunning(); this.http1Config = http1Config; this.h2Config = null; } /** * @since 5.3 */ public void configure(final HttpProcessor httpProcessor) { ensureNotRunning(); this.httpProcessor = httpProcessor; } /** * @since 5.3 */ public void configure(final Decorator exchangeHandlerDecorator) { ensureNotRunning(); this.exchangeHandlerDecorator = exchangeHandlerDecorator; } /** * @deprecated Use {@link #startExecution(IOEventHandlerFactory)}. */ @Deprecated public void start(final IOEventHandlerFactory handlerFactory) throws IOException { execute(handlerFactory); } /** * @deprecated Use {@link #configure(Http1Config)}, {@link #configure(HttpProcessor)}, {@link #configure(Decorator)}, {@link #start()}. */ @Deprecated public InetSocketAddress start( final HttpProcessor httpProcessor, final Decorator exchangeHandlerDecorator, final Http1Config http1Config) throws Exception { configure(http1Config); configure(exchangeHandlerDecorator); configure(httpProcessor); return start(); } /** * @deprecated Use {@link #configure(H2Config)}, {@link #configure(HttpProcessor)}, {@link #configure(Decorator)}, {@link #start()}. */ @Deprecated public InetSocketAddress start( final HttpProcessor httpProcessor, final Decorator exchangeHandlerDecorator, final H2Config h2Config) throws Exception { configure(h2Config); configure(exchangeHandlerDecorator); configure(httpProcessor); return start(); } /** * @deprecated Use {@link #configure(Http1Config)}, {@link #configure(HttpProcessor)}, {@link #start()}. */ @Deprecated public InetSocketAddress start(final HttpProcessor httpProcessor, final Http1Config http1Config) throws Exception { configure(http1Config); configure(httpProcessor); return start(); } /** * @deprecated Use {@link #configure(H2Config)}, {@link #start()}. */ @Deprecated public InetSocketAddress start(final H2Config h2Config) throws Exception { configure(h2Config); return start(); } /** * @deprecated Use {@link #configure(Http1Config)}, {@link #start()}. */ @Deprecated public InetSocketAddress start(final Http1Config http1Config) throws Exception { configure(http1Config); return start(); } public InetSocketAddress start() throws Exception { if (http1Config != null) { return startExecution(new InternalServerProtocolNegotiationStarter( httpProcessor != null ? httpProcessor : HttpProcessors.server(), new DefaultAsyncResponseExchangeHandlerFactory( RequestRouter.create(RequestRouter.LOCAL_AUTHORITY, UriPatternType.URI_PATTERN, routeEntries, RequestRouter.LOCAL_AUTHORITY_RESOLVER, null), exchangeHandlerDecorator != null ? exchangeHandlerDecorator : BasicAsyncServerExpectationDecorator::new), HttpVersionPolicy.FORCE_HTTP_1, H2Config.DEFAULT, http1Config, CharCodingConfig.DEFAULT, sslContext, sslSessionInitializer, sslSessionVerifier, LoggingExceptionCallback.INSTANCE)); } else { return startExecution(new InternalServerProtocolNegotiationStarter( httpProcessor != null ? httpProcessor : H2Processors.server(), new DefaultAsyncResponseExchangeHandlerFactory( RequestRouter.create(RequestRouter.LOCAL_AUTHORITY, UriPatternType.URI_PATTERN, routeEntries, RequestRouter.LOCAL_AUTHORITY_RESOLVER, null), exchangeHandlerDecorator != null ? exchangeHandlerDecorator : BasicAsyncServerExpectationDecorator::new), HttpVersionPolicy.FORCE_HTTP_2, h2Config, Http1Config.DEFAULT, CharCodingConfig.DEFAULT, sslContext, sslSessionInitializer, sslSessionVerifier, LoggingExceptionCallback.INSTANCE)); } } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/Http1TestClient.java0100644 0000000 0000000 00000010466 00000250600 027341 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import javax.net.ssl.SSLContext; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.impl.HttpProcessors; import org.apache.hc.core5.http.nio.NHttpMessageParserFactory; import org.apache.hc.core5.http.nio.NHttpMessageWriterFactory; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; public class Http1TestClient extends HttpTestClient { private Http1Config http1Config; private NHttpMessageParserFactory responseParserFactory; private NHttpMessageWriterFactory requestWriterFactory; public Http1TestClient( final IOReactorConfig ioReactorConfig, final SSLContext sslContext, final SSLSessionInitializer sslSessionInitializer, final SSLSessionVerifier sslSessionVerifier) throws IOException { super(ioReactorConfig, sslContext, sslSessionInitializer, sslSessionVerifier); } public Http1TestClient() throws IOException { this(IOReactorConfig.DEFAULT, null, null, null); } /** * @since 5.3 */ public void configure(final Http1Config http1Config) { ensureNotRunning(); this.http1Config = http1Config; } /** * @since 5.4 */ public void configure(final NHttpMessageParserFactory responseParserFactory) { ensureNotRunning(); this.responseParserFactory = responseParserFactory; } /** * @since 5.4 */ public void configure(final NHttpMessageWriterFactory requestWriterFactory) { ensureNotRunning(); this.requestWriterFactory = requestWriterFactory; } /** * @deprecated Use {@link #configure(Http1Config)}, {@link #configure(HttpProcessor)}, {@link #start()}. */ @Deprecated public void start( final HttpProcessor httpProcessor, final Http1Config http1Config) throws IOException { configure(http1Config); configure(httpProcessor); start(); } /** * @deprecated Use {@link #configure(Http1Config)}, {@link #start()}. */ @Deprecated public void start(final Http1Config http1Config) throws IOException { start(null, http1Config); } @Override public void start() throws IOException { startExecution(new InternalClientHttp1EventHandlerFactory( httpProcessor != null ? httpProcessor : HttpProcessors.client(), http1Config, CharCodingConfig.DEFAULT, DefaultConnectionReuseStrategy.INSTANCE, responseParserFactory, requestWriterFactory, sslContext, sslSessionInitializer, sslSessionVerifier)); } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/Http1TestServer.java0100644 0000000 0000000 00000013322 00000250600 027363 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import java.net.InetSocketAddress; import javax.net.ssl.SSLContext; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.impl.HttpProcessors; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.NHttpMessageParserFactory; import org.apache.hc.core5.http.nio.NHttpMessageWriterFactory; import org.apache.hc.core5.http.nio.support.BasicAsyncServerExpectationDecorator; import org.apache.hc.core5.http.nio.support.DefaultAsyncResponseExchangeHandlerFactory; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http.protocol.UriPatternType; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; public class Http1TestServer extends HttpTestServer { private Http1Config http1Config; private NHttpMessageParserFactory requestParserFactory; private NHttpMessageWriterFactory responseWriterFactory; public Http1TestServer( final IOReactorConfig ioReactorConfig, final SSLContext sslContext, final SSLSessionInitializer sslSessionInitializer, final SSLSessionVerifier sslSessionVerifier) throws IOException { super(ioReactorConfig, sslContext, sslSessionInitializer, sslSessionVerifier); } public Http1TestServer() throws IOException { this(IOReactorConfig.DEFAULT, null, null, null); } /** * @since 5.3 */ public void configure(final Http1Config http1Config) { ensureNotRunning(); this.http1Config = http1Config; } /** * @since 5.4 */ public void configure(final NHttpMessageParserFactory requestParserFactory) { ensureNotRunning(); this.requestParserFactory = requestParserFactory; } /** * @since 5.4 */ public void configure(final NHttpMessageWriterFactory responseWriterFactory) { ensureNotRunning(); this.responseWriterFactory = responseWriterFactory; } /** * @deprecated Use {@link #startExecution(IOEventHandlerFactory)}. */ @Deprecated public InetSocketAddress start(final IOEventHandlerFactory handlerFactory) throws Exception { return startExecution(handlerFactory); } /** * @deprecated Use {@link #configure(Http1Config)}, {@link #configure(HttpProcessor)}, {@link #configure(Decorator)}, {@link #start()}. */ @Deprecated public InetSocketAddress start( final HttpProcessor httpProcessor, final Decorator exchangeHandlerDecorator, final Http1Config http1Config) throws Exception { configure(http1Config); configure(exchangeHandlerDecorator); configure(httpProcessor); return start(); } /** * @deprecated Use {@link #configure(Http1Config)}, {@link #configure(HttpProcessor)}, {@link #start()}. */ @Deprecated public InetSocketAddress start(final HttpProcessor httpProcessor, final Http1Config http1Config) throws Exception { configure(http1Config); configure(httpProcessor); return start(); } @Override public InetSocketAddress start() throws Exception { return startExecution(new InternalServerHttp1EventHandlerFactory( httpProcessor != null ? httpProcessor : HttpProcessors.server(), new DefaultAsyncResponseExchangeHandlerFactory( RequestRouter.create(RequestRouter.LOCAL_AUTHORITY, UriPatternType.URI_PATTERN, routeEntries, RequestRouter.LOCAL_AUTHORITY_RESOLVER, null), exchangeHandlerDecorator != null ? exchangeHandlerDecorator : BasicAsyncServerExpectationDecorator::new), http1Config, CharCodingConfig.DEFAULT, DefaultConnectionReuseStrategy.INSTANCE, requestParserFactory, responseWriterFactory, sslContext, sslSessionInitializer, sslSessionVerifier)); } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/HttpTestClient.java0100644 0000000 0000000 00000007642 00000250600 027262 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import java.util.concurrent.Future; import javax.net.ssl.SSLContext; import org.apache.hc.core5.concurrent.BasicFuture; import org.apache.hc.core5.concurrent.CompletingFutureContribution; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.IOReactorStatus; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; import org.apache.hc.core5.util.Asserts; import org.apache.hc.core5.util.Timeout; /** * @since 5.4 */ public abstract class HttpTestClient extends AsyncRequester { final SSLContext sslContext; final SSLSessionInitializer sslSessionInitializer; final SSLSessionVerifier sslSessionVerifier; HttpProcessor httpProcessor; public HttpTestClient( final IOReactorConfig ioReactorConfig, final SSLContext sslContext, final SSLSessionInitializer sslSessionInitializer, final SSLSessionVerifier sslSessionVerifier) throws IOException { super(ioReactorConfig); this.sslContext = sslContext; this.sslSessionInitializer = sslSessionInitializer; this.sslSessionVerifier = sslSessionVerifier; } public HttpTestClient() throws IOException { this(IOReactorConfig.DEFAULT, null, null, null); } public abstract void start() throws Exception; void ensureNotRunning() { Asserts.check(getStatus() == IOReactorStatus.INACTIVE, "Client is already running"); } public void configure(final HttpProcessor httpProcessor) { ensureNotRunning(); this.httpProcessor = httpProcessor; } public Future connect( final HttpHost host, final Timeout timeout, final FutureCallback callback) { final BasicFuture future = new BasicFuture<>(callback); requestSession(host, timeout, new CompletingFutureContribution<>(future, ClientSessionEndpoint::new)); return future; } public Future connect(final HttpHost host, final Timeout timeout) { return connect(host, timeout, null); } public Future connect(final String hostname, final int port, final Timeout timeout) { return connect(new HttpHost(hostname, port), timeout, null); } public void startExecution(final IOEventHandlerFactory handlerFactory) throws IOException { super.execute(handlerFactory); } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/HttpTestServer.java0100644 0000000 0000000 00000011362 00000250600 027304 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Future; import javax.net.ssl.SSLContext; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.AsyncServerRequestHandler; import org.apache.hc.core5.http.nio.support.BasicServerExchangeHandler; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.IOReactorStatus; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; /** * @since 5.4 */ public abstract class HttpTestServer extends AsyncServer { final SSLContext sslContext; final SSLSessionInitializer sslSessionInitializer; final SSLSessionVerifier sslSessionVerifier; final List>> routeEntries; HttpProcessor httpProcessor; Decorator exchangeHandlerDecorator; public HttpTestServer( final IOReactorConfig ioReactorConfig, final SSLContext sslContext, final SSLSessionInitializer sslSessionInitializer, final SSLSessionVerifier sslSessionVerifier) throws IOException { super(ioReactorConfig); this.sslContext = sslContext; this.sslSessionInitializer = sslSessionInitializer; this.sslSessionVerifier = sslSessionVerifier; this.routeEntries = new ArrayList<>(); } public HttpTestServer() throws IOException { this(IOReactorConfig.DEFAULT, null, null, null); } public abstract InetSocketAddress start() throws Exception; void ensureNotRunning() { Asserts.check(getStatus() == IOReactorStatus.INACTIVE, "Server is already running"); } public void register(final String uriPattern, final Supplier supplier) { Args.notNull(uriPattern, "URI pattern"); Args.notNull(supplier, "Exchange handler supplier"); Asserts.check(getStatus() == IOReactorStatus.INACTIVE, "Server has already been started"); ensureNotRunning(); routeEntries.add(new RequestRouter.Entry<>(uriPattern, supplier)); } public void register( final String uriPattern, final AsyncServerRequestHandler requestHandler) { register(uriPattern, () -> new BasicServerExchangeHandler<>(requestHandler)); } public void configure(final HttpProcessor httpProcessor) { ensureNotRunning(); this.httpProcessor = httpProcessor; } public void configure(final Decorator exchangeHandlerDecorator) { ensureNotRunning(); this.exchangeHandlerDecorator = exchangeHandlerDecorator; } public InetSocketAddress startExecution(final IOEventHandlerFactory handlerFactory) throws Exception { execute(handlerFactory); final Future future = listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); final ListenerEndpoint listener = future.get(); return (InetSocketAddress) listener.getAddress(); } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/IOReactorExecutor.java0100644 0000000 0000000 00000012067 00000250600 027707 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.nio.command.ShutdownCommand; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.IOReactorService; import org.apache.hc.core5.reactor.IOReactorStatus; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; import org.apache.hc.core5.util.TimeValue; abstract class IOReactorExecutor implements AutoCloseable { enum Status { READY, RUNNING, TERMINATED } private final IOReactorConfig ioReactorConfig; private final ThreadFactory workerThreadFactory; private final AtomicReference ioReactorRef; private final AtomicReference status; IOReactorExecutor(final IOReactorConfig ioReactorConfig, final ThreadFactory workerThreadFactory) { super(); this.ioReactorConfig = ioReactorConfig != null ? ioReactorConfig : IOReactorConfig.DEFAULT; this.workerThreadFactory = workerThreadFactory; this.ioReactorRef = new AtomicReference<>(); this.status = new AtomicReference<>(Status.READY); } abstract T createIOReactor( IOEventHandlerFactory ioEventHandlerFactory, IOReactorConfig ioReactorConfig, ThreadFactory threadFactory, Callback sessionShutdownCallback) throws IOException; protected void execute(final IOEventHandlerFactory ioEventHandlerFactory) throws IOException { Args.notNull(ioEventHandlerFactory, "Handler factory"); if (ioReactorRef.compareAndSet(null, createIOReactor( ioEventHandlerFactory, ioReactorConfig, workerThreadFactory, ShutdownCommand.GRACEFUL_NORMAL_CALLBACK))) { if (status.compareAndSet(Status.READY, Status.RUNNING)) { ioReactorRef.get().start(); } } else { throw new IllegalStateException("I/O reactor has already been started"); } } private T ensureRunning() { final T ioReactor = ioReactorRef.get(); Asserts.check(ioReactor != null, "I/O reactor has not been started"); return ioReactor; } T reactor() { return ensureRunning(); } public IOReactorStatus getStatus() { final T ioReactor = ioReactorRef.get(); return ioReactor != null ? ioReactor.getStatus() : IOReactorStatus.INACTIVE; } public void awaitShutdown(final TimeValue waitTime) throws InterruptedException { Args.notNull(waitTime, "Wait time"); final T ioReactor = ioReactorRef.get(); if (ioReactor != null) { ioReactor.awaitShutdown(waitTime); } } public void initiateShutdown() { final T ioReactor = ioReactorRef.get(); if (ioReactor != null) { if (status.compareAndSet(Status.RUNNING, Status.TERMINATED)) { ioReactor.initiateShutdown(); } } } public void shutdown(final TimeValue graceTime) { Args.notNull(graceTime, "Grace time"); final T ioReactor = ioReactorRef.get(); if (ioReactor != null) { if (status.compareAndSet(Status.RUNNING, Status.TERMINATED)) { ioReactor.initiateShutdown(); } try { ioReactor.awaitShutdown(graceTime); } catch (final InterruptedException ex) { Thread.currentThread().interrupt(); } ioReactor.close(CloseMode.IMMEDIATE); } } @Override public void close() throws Exception { shutdown(TimeValue.ofSeconds(5)); } } ././@LongLink0100644 0000000 0000000 00000000154 00000250600 011611 Lustar 0000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalClientHttp1EventHandlerFactory.javahttpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalClientHttp1EventHandlerFacto0100644 0000000 0000000 00000015045 00000250600 032531 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import javax.net.ssl.SSLContext; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.nio.ClientHttp1IOEventHandler; import org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexer; import org.apache.hc.core5.http.impl.nio.DefaultHttpRequestWriterFactory; import org.apache.hc.core5.http.impl.nio.DefaultHttpResponseParserFactory; import org.apache.hc.core5.http.nio.NHttpMessageParser; import org.apache.hc.core5.http.nio.NHttpMessageParserFactory; import org.apache.hc.core5.http.nio.NHttpMessageWriter; import org.apache.hc.core5.http.nio.NHttpMessageWriterFactory; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.reactor.IOEventHandler; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; import org.apache.hc.core5.util.Args; /** * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) class InternalClientHttp1EventHandlerFactory implements IOEventHandlerFactory { private final HttpProcessor httpProcessor; private final Http1Config http1Config; private final CharCodingConfig charCodingConfig; private final ConnectionReuseStrategy connectionReuseStrategy; private final SSLContext sslContext; private final SSLSessionInitializer sslSessionInitializer; private final SSLSessionVerifier sslSessionVerifier; private final NHttpMessageParserFactory responseParserFactory; private final NHttpMessageWriterFactory requestWriterFactory; InternalClientHttp1EventHandlerFactory( final HttpProcessor httpProcessor, final Http1Config http1Config, final CharCodingConfig charCodingConfig, final ConnectionReuseStrategy connectionReuseStrategy, final NHttpMessageParserFactory responseParserFactory, final NHttpMessageWriterFactory requestWriterFactory, final SSLContext sslContext, final SSLSessionInitializer sslSessionInitializer, final SSLSessionVerifier sslSessionVerifier) { this.httpProcessor = Args.notNull(httpProcessor, "HTTP processor"); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.charCodingConfig = charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT; this.connectionReuseStrategy = connectionReuseStrategy != null ? connectionReuseStrategy : DefaultConnectionReuseStrategy.INSTANCE; this.sslContext = sslContext; this.responseParserFactory = responseParserFactory != null ? responseParserFactory : new DefaultHttpResponseParserFactory(this.http1Config); this.requestWriterFactory = requestWriterFactory != null ? requestWriterFactory : DefaultHttpRequestWriterFactory.INSTANCE; this.sslSessionInitializer = sslSessionInitializer; this.sslSessionVerifier = sslSessionVerifier; } protected ClientHttp1StreamDuplexer createClientHttp1StreamDuplexer( final ProtocolIOSession ioSession, final HttpProcessor httpProcessor, final Http1Config http1Config, final CharCodingConfig charCodingConfig, final ConnectionReuseStrategy connectionReuseStrategy, final NHttpMessageParser incomingMessageParser, final NHttpMessageWriter outgoingMessageWriter, final ContentLengthStrategy incomingContentStrategy, final ContentLengthStrategy outgoingContentStrategy, final Http1StreamListener streamListener) { return new ClientHttp1StreamDuplexer(ioSession, httpProcessor, http1Config, charCodingConfig, connectionReuseStrategy, incomingMessageParser, outgoingMessageWriter, incomingContentStrategy, outgoingContentStrategy, streamListener); } @Override public IOEventHandler createHandler(final ProtocolIOSession ioSession, final Object attachment) { if (sslContext != null) { ioSession.startTls(sslContext, null, null, sslSessionInitializer, sslSessionVerifier, null); } final ClientHttp1StreamDuplexer streamDuplexer = createClientHttp1StreamDuplexer( ioSession, httpProcessor, http1Config, charCodingConfig, connectionReuseStrategy, responseParserFactory.create(), requestWriterFactory.create(), DefaultContentLengthStrategy.INSTANCE, DefaultContentLengthStrategy.INSTANCE, LoggingHttp1StreamListener.INSTANCE_CLIENT); return new ClientHttp1IOEventHandler(streamDuplexer); } } ././@LongLink0100644 0000000 0000000 00000000156 00000250600 011613 Lustar 0000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalClientProtocolNegotiationStarter.javahttpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalClientProtocolNegotiationSta0100644 0000000 0000000 00000014011 00000250600 032716 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import javax.net.ssl.SSLContext; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.HttpProcessors; import org.apache.hc.core5.http.impl.nio.ClientHttp1IOEventHandler; import org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexerFactory; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.impl.H2Processors; import org.apache.hc.core5.http2.impl.nio.ClientH2PrefaceHandler; import org.apache.hc.core5.http2.impl.nio.ClientH2StreamMultiplexerFactory; import org.apache.hc.core5.http2.impl.nio.ClientH2UpgradeHandler; import org.apache.hc.core5.http2.impl.nio.ClientHttp1UpgradeHandler; import org.apache.hc.core5.http2.impl.nio.HttpProtocolNegotiator; import org.apache.hc.core5.http2.ssl.ApplicationProtocol; import org.apache.hc.core5.reactor.IOEventHandler; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; import org.apache.hc.core5.util.Args; class InternalClientProtocolNegotiationStarter implements IOEventHandlerFactory { private final HttpProcessor httpProcessor; private final HandlerFactory exchangeHandlerFactory; private final HttpVersionPolicy versionPolicy; private final H2Config h2Config; private final Http1Config http1Config; private final CharCodingConfig charCodingConfig; private final SSLContext sslContext; private final SSLSessionInitializer sslSessionInitializer; private final SSLSessionVerifier sslSessionVerifier; private final Callback exceptionCallback; InternalClientProtocolNegotiationStarter( final HttpProcessor httpProcessor, final HandlerFactory exchangeHandlerFactory, final HttpVersionPolicy versionPolicy, final H2Config h2Config, final Http1Config http1Config, final CharCodingConfig charCodingConfig, final SSLContext sslContext, final SSLSessionInitializer sslSessionInitializer, final SSLSessionVerifier sslSessionVerifier, final Callback exceptionCallback) { this.httpProcessor = Args.notNull(httpProcessor, "HTTP processor"); this.exchangeHandlerFactory = exchangeHandlerFactory; this.versionPolicy = versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE; this.h2Config = h2Config != null ? h2Config : H2Config.DEFAULT; this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.charCodingConfig = charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT; this.sslContext = sslContext; this.sslSessionInitializer = sslSessionInitializer; this.sslSessionVerifier = sslSessionVerifier; this.exceptionCallback = exceptionCallback; } @Override public IOEventHandler createHandler(final ProtocolIOSession ioSession, final Object attachment) { if (sslContext != null) { ioSession.startTls(sslContext, null, null, sslSessionInitializer, sslSessionVerifier, null); } final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory = new ClientHttp1StreamDuplexerFactory( httpProcessor != null ? httpProcessor : HttpProcessors.client(), http1Config, charCodingConfig, LoggingHttp1StreamListener.INSTANCE_CLIENT); final ClientH2StreamMultiplexerFactory http2StreamHandlerFactory = new ClientH2StreamMultiplexerFactory( httpProcessor != null ? httpProcessor : H2Processors.client(), exchangeHandlerFactory, h2Config, charCodingConfig, LoggingH2StreamListener.INSTANCE); ioSession.registerProtocol(ApplicationProtocol.HTTP_1_1.id, new ClientHttp1UpgradeHandler(http1StreamHandlerFactory)); ioSession.registerProtocol(ApplicationProtocol.HTTP_2.id, new ClientH2UpgradeHandler(http2StreamHandlerFactory, exceptionCallback)); switch (versionPolicy) { case FORCE_HTTP_2: return new ClientH2PrefaceHandler(ioSession, http2StreamHandlerFactory, false, exceptionCallback); case FORCE_HTTP_1: return new ClientHttp1IOEventHandler(http1StreamHandlerFactory.create(ioSession)); default: return new HttpProtocolNegotiator(ioSession, null); } } } ././@LongLink0100644 0000000 0000000 00000000154 00000250600 011611 Lustar 0000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalServerHttp1EventHandlerFactory.javahttpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalServerHttp1EventHandlerFacto0100644 0000000 0000000 00000016562 00000250600 032566 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import javax.net.ssl.SSLContext; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.nio.DefaultHttpRequestParserFactory; import org.apache.hc.core5.http.impl.nio.DefaultHttpResponseWriterFactory; import org.apache.hc.core5.http.impl.nio.ServerHttp1IOEventHandler; import org.apache.hc.core5.http.impl.nio.ServerHttp1StreamDuplexer; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.nio.NHttpMessageParser; import org.apache.hc.core5.http.nio.NHttpMessageParserFactory; import org.apache.hc.core5.http.nio.NHttpMessageWriter; import org.apache.hc.core5.http.nio.NHttpMessageWriterFactory; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.reactor.IOEventHandler; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; import org.apache.hc.core5.util.Args; /** * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) class InternalServerHttp1EventHandlerFactory implements IOEventHandlerFactory { private final HttpProcessor httpProcessor; private final HandlerFactory exchangeHandlerFactory; private final Http1Config http1Config; private final CharCodingConfig charCodingConfig; private final ConnectionReuseStrategy connectionReuseStrategy; private final SSLContext sslContext; private final SSLSessionInitializer sslSessionInitializer; private final SSLSessionVerifier sslSessionVerifier; private final NHttpMessageParserFactory requestParserFactory; private final NHttpMessageWriterFactory responseWriterFactory; InternalServerHttp1EventHandlerFactory( final HttpProcessor httpProcessor, final HandlerFactory exchangeHandlerFactory, final Http1Config http1Config, final CharCodingConfig charCodingConfig, final ConnectionReuseStrategy connectionReuseStrategy, final NHttpMessageParserFactory requestParserFactory, final NHttpMessageWriterFactory responseWriterFactory, final SSLContext sslContext, final SSLSessionInitializer sslSessionInitializer, final SSLSessionVerifier sslSessionVerifier) { this.httpProcessor = Args.notNull(httpProcessor, "HTTP processor"); this.exchangeHandlerFactory = Args.notNull(exchangeHandlerFactory, "Exchange handler factory"); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.charCodingConfig = charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT; this.connectionReuseStrategy = connectionReuseStrategy != null ? connectionReuseStrategy : DefaultConnectionReuseStrategy.INSTANCE; this.sslContext = sslContext; this.sslSessionInitializer = sslSessionInitializer; this.sslSessionVerifier = sslSessionVerifier; this.requestParserFactory = requestParserFactory != null ? requestParserFactory : new DefaultHttpRequestParserFactory(this.http1Config); this.responseWriterFactory = responseWriterFactory != null ? responseWriterFactory : new DefaultHttpResponseWriterFactory(this.http1Config); } protected ServerHttp1StreamDuplexer createServerHttp1StreamDuplexer( final ProtocolIOSession ioSession, final HttpProcessor httpProcessor, final HandlerFactory exchangeHandlerFactory, final Http1Config http1Config, final CharCodingConfig charCodingConfig, final ConnectionReuseStrategy connectionReuseStrategy, final NHttpMessageParser incomingMessageParser, final NHttpMessageWriter outgoingMessageWriter, final ContentLengthStrategy incomingContentStrategy, final ContentLengthStrategy outgoingContentStrategy, final Http1StreamListener streamListener, final Callback exceptionCallback) { return new ServerHttp1StreamDuplexer(ioSession, httpProcessor, exchangeHandlerFactory, sslContext != null ? URIScheme.HTTPS.id : URIScheme.HTTP.id, http1Config, charCodingConfig, connectionReuseStrategy, incomingMessageParser, outgoingMessageWriter, incomingContentStrategy, outgoingContentStrategy, streamListener, exceptionCallback); } @Override public IOEventHandler createHandler(final ProtocolIOSession ioSession, final Object attachment) { if (sslContext != null) { ioSession.startTls(sslContext, null, null ,sslSessionInitializer, sslSessionVerifier, null); } final ServerHttp1StreamDuplexer streamDuplexer = createServerHttp1StreamDuplexer( ioSession, httpProcessor, exchangeHandlerFactory, http1Config, charCodingConfig, connectionReuseStrategy, requestParserFactory.create(), responseWriterFactory.create(), DefaultContentLengthStrategy.INSTANCE, DefaultContentLengthStrategy.INSTANCE, LoggingHttp1StreamListener.INSTANCE_SERVER, LoggingExceptionCallback.INSTANCE); return new ServerHttp1IOEventHandler(streamDuplexer); } } ././@LongLink0100644 0000000 0000000 00000000156 00000250600 011613 Lustar 0000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalServerProtocolNegotiationStarter.javahttpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalServerProtocolNegotiationSta0100644 0000000 0000000 00000014503 00000250600 032754 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import javax.net.ssl.SSLContext; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.HttpProcessors; import org.apache.hc.core5.http.impl.nio.ServerHttp1IOEventHandler; import org.apache.hc.core5.http.impl.nio.ServerHttp1StreamDuplexerFactory; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.impl.H2Processors; import org.apache.hc.core5.http2.impl.nio.HttpProtocolNegotiator; import org.apache.hc.core5.http2.impl.nio.ServerH2PrefaceHandler; import org.apache.hc.core5.http2.impl.nio.ServerH2StreamMultiplexerFactory; import org.apache.hc.core5.http2.impl.nio.ServerH2UpgradeHandler; import org.apache.hc.core5.http2.impl.nio.ServerHttp1UpgradeHandler; import org.apache.hc.core5.http2.ssl.ApplicationProtocol; import org.apache.hc.core5.reactor.IOEventHandler; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; import org.apache.hc.core5.util.Args; class InternalServerProtocolNegotiationStarter implements IOEventHandlerFactory { private final HttpProcessor httpProcessor; private final HandlerFactory exchangeHandlerFactory; private final HttpVersionPolicy versionPolicy; private final H2Config h2Config; private final Http1Config http1Config; private final CharCodingConfig charCodingConfig; private final SSLContext sslContext; private final SSLSessionInitializer sslSessionInitializer; private final SSLSessionVerifier sslSessionVerifier; private final Callback exceptionCallback; public InternalServerProtocolNegotiationStarter( final HttpProcessor httpProcessor, final HandlerFactory exchangeHandlerFactory, final HttpVersionPolicy versionPolicy, final H2Config h2Config, final Http1Config http1Config, final CharCodingConfig charCodingConfig, final SSLContext sslContext, final SSLSessionInitializer sslSessionInitializer, final SSLSessionVerifier sslSessionVerifier, final Callback exceptionCallback) { this.httpProcessor = Args.notNull(httpProcessor, "HTTP processor"); this.exchangeHandlerFactory = Args.notNull(exchangeHandlerFactory, "Exchange handler factory"); this.versionPolicy = versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE; this.h2Config = h2Config != null ? h2Config : H2Config.DEFAULT; this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.charCodingConfig = charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT; this.sslContext = sslContext; this.sslSessionInitializer = sslSessionInitializer; this.sslSessionVerifier = sslSessionVerifier; this.exceptionCallback = exceptionCallback; } @Override public IOEventHandler createHandler(final ProtocolIOSession ioSession, final Object attachment) { if (sslContext != null) { ioSession.startTls(sslContext, null, null, sslSessionInitializer, sslSessionVerifier, null); } final ServerHttp1StreamDuplexerFactory http1StreamHandlerFactory = new ServerHttp1StreamDuplexerFactory( httpProcessor != null ? httpProcessor : HttpProcessors.server(), exchangeHandlerFactory, http1Config, charCodingConfig, LoggingHttp1StreamListener.INSTANCE_SERVER, LoggingExceptionCallback.INSTANCE); final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory = new ServerH2StreamMultiplexerFactory( httpProcessor != null ? httpProcessor : H2Processors.server(), exchangeHandlerFactory, h2Config, charCodingConfig, LoggingH2StreamListener.INSTANCE); ioSession.registerProtocol(ApplicationProtocol.HTTP_1_1.id, new ServerHttp1UpgradeHandler(http1StreamHandlerFactory)); ioSession.registerProtocol(ApplicationProtocol.HTTP_2.id, new ServerH2UpgradeHandler(http2StreamHandlerFactory, exceptionCallback)); switch (versionPolicy) { case FORCE_HTTP_2: return new ServerH2PrefaceHandler(ioSession, http2StreamHandlerFactory, exceptionCallback); case FORCE_HTTP_1: return new ServerHttp1IOEventHandler(http1StreamHandlerFactory.create( sslContext != null ? URIScheme.HTTPS.id : URIScheme.HTTP.id, ioSession)); default: return new HttpProtocolNegotiator(ioSession, null); } } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/LogAppendable.java0100644 0000000 0000000 00000004572 00000250600 027040 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import org.slf4j.Logger; class LogAppendable implements Appendable { private final Logger log; private final String prefix; private final StringBuilder buffer; LogAppendable(final Logger log, final String prefix) { this.log = log; this.prefix = prefix; this.buffer = new StringBuilder(); } @Override public Appendable append(final CharSequence text) throws IOException { return append(text, 0, text.length()); } @Override public Appendable append(final CharSequence text, final int start, final int end) throws IOException { for (int i = start; i < end; i++) { append(text.charAt(i)); } return this; } @Override public Appendable append(final char ch) throws IOException { if (ch == '\n') { log.debug("{} {}", prefix, buffer); buffer.setLength(0); } else if (ch != '\r') { buffer.append(ch); } return this; } public void flush() { if (buffer.length() > 0) { log.debug("{} {}", prefix, buffer); buffer.setLength(0); } } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/LoggingExceptionCallback.java0100644 0000000 0000000 00000003315 00000250600 031217 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import org.apache.hc.core5.function.Callback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LoggingExceptionCallback implements Callback { public final static LoggingExceptionCallback INSTANCE = new LoggingExceptionCallback(); private final Logger log = LoggerFactory.getLogger("org.apache.hc.core5.reactor"); private LoggingExceptionCallback() { } @Override public void execute(final Exception ex) { log.error(ex.getMessage(), ex); } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/LoggingH2StreamListener.java0100644 0000000 0000000 00000012726 00000250600 031005 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import java.util.List; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http2.frame.FramePrinter; import org.apache.hc.core5.http2.frame.RawFrame; import org.apache.hc.core5.http2.impl.nio.H2StreamListener; import org.apache.hc.core5.testing.classic.LoggingSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LoggingH2StreamListener implements H2StreamListener { public final static LoggingH2StreamListener INSTANCE = new LoggingH2StreamListener(); private final Logger headerLog; private final Logger frameLog; private final Logger framePayloadLog; private final Logger flowCtrlLog; private final FramePrinter framePrinter; private LoggingH2StreamListener() { this.framePrinter = new FramePrinter(); this.headerLog = LoggerFactory.getLogger("org.apache.hc.core5.http.headers"); this.frameLog = LoggerFactory.getLogger("org.apache.hc.core5.http2.frame"); this.framePayloadLog = LoggerFactory.getLogger("org.apache.hc.core5.http2.frame.payload"); this.flowCtrlLog = LoggerFactory.getLogger("org.apache.hc.core5.http2.flow"); } private void logFrameInfo(final String prefix, final RawFrame frame) { try { final LogAppendable logAppendable = new LogAppendable(frameLog, prefix); framePrinter.printFrameInfo(frame, logAppendable); logAppendable.flush(); } catch (final IOException ignore) { // ignore } } private void logFramePayload(final String prefix, final RawFrame frame) { try { final LogAppendable logAppendable = new LogAppendable(framePayloadLog, prefix); framePrinter.printPayload(frame, logAppendable); logAppendable.flush(); } catch (final IOException ignore) { // ignore } } private void logFlowControl(final String prefix, final int streamId, final int delta, final int actualSize) { flowCtrlLog.debug("{} stream {} flow control {} -> {}", prefix, streamId, delta, actualSize); } @Override public void onHeaderInput(final HttpConnection connection, final int streamId, final List headers) { if (headerLog.isDebugEnabled()) { final String prefix = LoggingSupport.getId(connection); for (int i = 0; i < headers.size(); i++) { headerLog.debug("{} << {}", prefix, headers.get(i)); } } } @Override public void onHeaderOutput(final HttpConnection connection, final int streamId, final List headers) { if (headerLog.isDebugEnabled()) { final String prefix = LoggingSupport.getId(connection); for (int i = 0; i < headers.size(); i++) { headerLog.debug("{} >> {}", prefix, headers.get(i)); } } } @Override public void onFrameInput(final HttpConnection connection, final int streamId, final RawFrame frame) { if (frameLog.isDebugEnabled()) { logFrameInfo(LoggingSupport.getId(connection) + " <<", frame); } if (framePayloadLog.isDebugEnabled()) { logFramePayload(LoggingSupport.getId(connection) + " <<", frame); } } @Override public void onFrameOutput(final HttpConnection connection, final int streamId, final RawFrame frame) { if (frameLog.isDebugEnabled()) { logFrameInfo(LoggingSupport.getId(connection) + " >>", frame); } if (framePayloadLog.isDebugEnabled()) { logFramePayload(LoggingSupport.getId(connection) + " >>", frame); } } @Override public void onInputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { if (flowCtrlLog.isDebugEnabled()) { logFlowControl(LoggingSupport.getId(connection) + " in", streamId, delta, actualSize); } } @Override public void onOutputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { if (flowCtrlLog.isDebugEnabled()) { logFlowControl(LoggingSupport.getId(connection) + " out", streamId, delta, actualSize); } } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/LoggingHttp1StreamListener.java0100644 0000000 0000000 00000007574 00000250600 031541 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.util.Iterator; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpConnection; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.testing.classic.LoggingSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LoggingHttp1StreamListener implements Http1StreamListener { enum Type { CLIENT, SERVER } public final static LoggingHttp1StreamListener INSTANCE_CLIENT = new LoggingHttp1StreamListener(Type.CLIENT); public final static LoggingHttp1StreamListener INSTANCE_SERVER = new LoggingHttp1StreamListener(Type.SERVER); private final Logger connLog = LoggerFactory.getLogger("org.apache.hc.core5.http.connection"); private final Logger headerLog = LoggerFactory.getLogger("org.apache.hc.core5.http.headers"); private final String requestDirection; private final String responseDirection; private LoggingHttp1StreamListener(final Type type) { this.requestDirection = type == Type.CLIENT ? " >> " : " << "; this.responseDirection = type == Type.CLIENT ? " << " : " >> "; } @Override public void onRequestHead(final HttpConnection connection, final HttpRequest request) { if (headerLog.isDebugEnabled()) { final String idRequestDirection = LoggingSupport.getId(connection) + requestDirection; headerLog.debug("{}{} {}", idRequestDirection, request.getMethod(), request.getRequestUri()); for (final Iterator
it = request.headerIterator(); it.hasNext(); ) { headerLog.debug("{}{}", idRequestDirection, it.next()); } } } @Override public void onResponseHead(final HttpConnection connection, final HttpResponse response) { if (headerLog.isDebugEnabled()) { final String id = LoggingSupport.getId(connection); headerLog.debug("{}{}{}", id, responseDirection, new StatusLine(response)); for (final Iterator
it = response.headerIterator(); it.hasNext(); ) { headerLog.debug("{}{}{}", id, responseDirection, it.next()); } } } @Override public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) { if (connLog.isDebugEnabled()) { if (keepAlive) { connLog.debug("{} connection is kept alive", LoggingSupport.getId(connection)); } else { connLog.debug("{} connection is not kept alive", LoggingSupport.getId(connection)); } } } }httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/LoggingIOSession.java0100644 0000000 0000000 00000024114 00000250600 027517 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.nio.channels.SelectionKey; import java.util.concurrent.locks.Lock; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.Chars; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.Command; import org.apache.hc.core5.reactor.IOEventHandler; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Timeout; import org.slf4j.Logger; @Internal public class LoggingIOSession implements IOSession { private final Logger log; private final Logger wireLog; private final IOSession session; public LoggingIOSession(final IOSession session, final Logger log, final Logger wireLog) { super(); this.session = session; this.log = log; this.wireLog = wireLog; } public LoggingIOSession(final ProtocolIOSession session, final Logger log) { this(session, log, null); } @Override public String getId() { return session.getId(); } @Override public Lock getLock() { return this.session.getLock(); } @Override public void enqueue(final Command command, final Command.Priority priority) { this.session.enqueue(command, priority); if (this.log.isDebugEnabled()) { this.log.debug("{} enqueued {} with priority {}", this.session, command.getClass().getSimpleName(), priority); } } @Override public boolean hasCommands() { return this.session.hasCommands(); } @Override public Command poll() { return this.session.poll(); } @Override public ByteChannel channel() { return this.session.channel(); } @Override public SocketAddress getLocalAddress() { return this.session.getLocalAddress(); } @Override public SocketAddress getRemoteAddress() { return this.session.getRemoteAddress(); } @Override public int getEventMask() { return this.session.getEventMask(); } private static String formatOps(final int ops) { final StringBuilder buffer = new StringBuilder(6); buffer.append('['); if ((ops & SelectionKey.OP_READ) > 0) { buffer.append('r'); } if ((ops & SelectionKey.OP_WRITE) > 0) { buffer.append('w'); } if ((ops & SelectionKey.OP_ACCEPT) > 0) { buffer.append('a'); } if ((ops & SelectionKey.OP_CONNECT) > 0) { buffer.append('c'); } buffer.append(']'); return buffer.toString(); } @Override public void setEventMask(final int ops) { this.session.setEventMask(ops); if (this.log.isDebugEnabled()) { this.log.debug("{} event mask set {}", this.session, formatOps(ops)); } } @Override public void setEvent(final int op) { this.session.setEvent(op); if (this.log.isDebugEnabled()) { this.log.debug("{} event set {}", this.session, formatOps(op)); } } @Override public void clearEvent(final int op) { this.session.clearEvent(op); if (this.log.isDebugEnabled()) { this.log.debug("{} event cleared {}", this.session, formatOps(op)); } } @Override public void close() { if (this.log.isDebugEnabled()) { this.log.debug("{} close", this.session); } this.session.close(); } @Override public Status getStatus() { return this.session.getStatus(); } @Override public boolean isOpen() { return session.isOpen(); } @Override public void close(final CloseMode closeMode) { if (this.log.isDebugEnabled()) { this.log.debug("{} shutdown {}", this.session, closeMode); } this.session.close(closeMode); } @Override public Timeout getSocketTimeout() { return this.session.getSocketTimeout(); } @Override public void setSocketTimeout(final Timeout timeout) { if (this.log.isDebugEnabled()) { this.log.debug("{} set timeout {}", this.session, timeout); } this.session.setSocketTimeout(timeout); } @Override public int read(final ByteBuffer dst) throws IOException { final int bytesRead = session.read(dst); if (log.isDebugEnabled()) { log.debug("{} {} bytes read", session, bytesRead); } if (bytesRead > 0 && wireLog.isDebugEnabled()) { final ByteBuffer b = dst.duplicate(); final int p = b.position(); b.limit(p); b.position(p - bytesRead); logData(b, "<< "); } return bytesRead; } @Override public int write(final ByteBuffer src) throws IOException { final int byteWritten = session.write(src); if (log.isDebugEnabled()) { log.debug("{} {} bytes written", session, byteWritten); } if (byteWritten > 0 && wireLog.isDebugEnabled()) { final ByteBuffer b = src.duplicate(); final int p = b.position(); b.limit(p); b.position(p - byteWritten); logData(b, ">> "); } return byteWritten; } private void logData(final ByteBuffer data, final String prefix) throws IOException { final byte[] line = new byte[16]; final StringBuilder buf = new StringBuilder(); while (data.hasRemaining()) { buf.setLength(0); buf.append(prefix); final int chunk = Math.min(data.remaining(), line.length); data.get(line, 0, chunk); for (int i = 0; i < chunk; i++) { final char ch = (char) line[i]; if (ch > Chars.SP && ch <= Chars.DEL) { buf.append(ch); } else if (Character.isWhitespace(ch)) { buf.append(' '); } else { buf.append('.'); } } for (int i = chunk; i < 17; i++) { buf.append(' '); } for (int i = 0; i < chunk; i++) { buf.append(' '); final int b = line[i] & 0xff; final String s = Integer.toHexString(b); if (s.length() == 1) { buf.append("0"); } buf.append(s); } wireLog.debug(buf.toString()); } } @Override public void updateReadTime() { this.session.updateReadTime(); } @Override public void updateWriteTime() { this.session.updateWriteTime(); } @Override public long getLastReadTime() { return this.session.getLastReadTime(); } @Override public long getLastWriteTime() { return this.session.getLastWriteTime(); } @Override public long getLastEventTime() { return this.session.getLastEventTime(); } @Override public IOEventHandler getHandler() { return this.session.getHandler(); } @Override public void upgrade(final IOEventHandler handler) { Args.notNull(handler, "Protocol handler"); if (this.log.isDebugEnabled()) { this.log.debug("{} protocol upgrade: {}", this.session, handler.getClass()); } this.session.upgrade(new IOEventHandler() { @Override public void connected(final IOSession protocolSession) throws IOException { handler.connected(protocolSession); } @Override public void inputReady(final IOSession protocolSession, final ByteBuffer src) throws IOException { if (src != null && wireLog.isDebugEnabled()) { final ByteBuffer b = src.duplicate(); logData(b, "<< "); } handler.inputReady(protocolSession, src); } @Override public void outputReady(final IOSession protocolSession) throws IOException { handler.outputReady(protocolSession); } @Override public void timeout(final IOSession protocolSession, final Timeout timeout) throws IOException { handler.timeout(protocolSession, timeout); } @Override public void exception(final IOSession protocolSession, final Exception cause) { handler.exception(protocolSession, cause); } @Override public void disconnected(final IOSession protocolSession) { handler.disconnected(protocolSession); } }); } @Override public String toString() { return this.session.toString(); } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/LoggingIOSessionDecorator.java0100644 0000000 0000000 00000003673 00000250600 031371 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.reactor.IOSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Internal public class LoggingIOSessionDecorator implements Decorator { public final static LoggingIOSessionDecorator INSTANCE = new LoggingIOSessionDecorator(); private final Logger wireLog = LoggerFactory.getLogger("org.apache.hc.core5.http.wire"); private LoggingIOSessionDecorator() { } @Override public IOSession decorate(final IOSession ioSession) { final Logger sessionLog = LoggerFactory.getLogger(ioSession.getClass()); return new LoggingIOSession(ioSession, sessionLog, wireLog); } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/LoggingIOSessionListener.java0100644 0000000 0000000 00000006066 00000250600 031233 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.IOSessionListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LoggingIOSessionListener implements IOSessionListener { public final static LoggingIOSessionListener INSTANCE = new LoggingIOSessionListener(); private final Logger connLog = LoggerFactory.getLogger("org.apache.hc.core5.http.connection"); @Internal public LoggingIOSessionListener() { } @Override public void connected(final IOSession session) { if (connLog.isDebugEnabled()) { connLog.debug("{} connected", session); } } @Override public void startTls(final IOSession session) { if (connLog.isDebugEnabled()) { connLog.debug("{} TLS started", session); } } @Override public void inputReady(final IOSession session) { if (connLog.isDebugEnabled()) { connLog.debug("{} input ready", session); } } @Override public void outputReady(final IOSession session) { if (connLog.isDebugEnabled()) { connLog.debug("{} output ready", session); } } @Override public void timeout(final IOSession session) { if (connLog.isDebugEnabled()) { connLog.debug("{} timeout", session); } } @Override public void exception(final IOSession session, final Exception ex) { if (ex instanceof ConnectionClosedException) { return; } connLog.error("{} {}", session, ex.getMessage(), ex); } @Override public void disconnected(final IOSession session) { if (connLog.isDebugEnabled()) { connLog.debug("{} disconnected", session); } } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/LoggingReactorMetricsListener.java0100644 0000000 0000000 00000004723 00000250600 032304 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import org.apache.hc.core5.reactor.IOReactorMetricsListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LoggingReactorMetricsListener implements IOReactorMetricsListener { public static final IOReactorMetricsListener INSTANCE = new LoggingReactorMetricsListener(); private final Logger logger = LoggerFactory.getLogger("org.apache.hc.core5.http.pool"); @Override public void onThreadPoolStatus(final int activeThreads, final int pendingConnections) { if (logger.isDebugEnabled()) { logger.debug("Active threads: {}, Pending connections: {}", activeThreads, pendingConnections); } } @Override public void onThreadPoolSaturation(final double saturationPercentage) { if (logger.isDebugEnabled()) { logger.debug("Thread pool saturation: {}%", saturationPercentage); } } @Override public void onResourceStarvationDetected() { if (logger.isDebugEnabled()) { logger.debug("Resource starvation detected!"); } } @Override public void onQueueWaitTime(final long averageWaitTimeMillis) { if (logger.isDebugEnabled()) { logger.debug("Average queue wait time: {} ms", averageWaitTimeMillis); } } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/reactive/0040755 0000000 0000000 00000000000 00000250600 024510 5ustar000000000 0000000 httpcore5-testing/src/main/java/org/apache/hc/core5/testing/reactive/Reactive3TestUtils.java0100644 0000000 0000000 00000014326 00000250600 031064 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.reactive; import io.reactivex.rxjava3.core.Emitter; import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.functions.Consumer; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.util.TextUtils; import org.reactivestreams.Publisher; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Random; import java.util.concurrent.atomic.AtomicReference; @Internal public class Reactive3TestUtils { /** The range from which to generate random data. */ private final static byte[] RANGE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" .getBytes(StandardCharsets.US_ASCII); /** * Produces a deterministic stream of bytes, in randomly sized chunks of up to 128kB. * * @param length the number of bytes in the stream * @return a reactive stream of bytes */ public static Flowable produceStream(final long length) { return produceStream(length, null); } /** * Produces a deterministic stream of bytes, in randomly sized chunks of up to 128kB, while computing the hash of * the random data. * * @param length the number of bytes in the stream * @param hash an output argument for the hash, set when the end of the stream is reached; if {@code null}, the * hash will not be computed * @return a reactive stream of bytes */ public static Flowable produceStream(final long length, final AtomicReference hash) { return produceStream(length, 128 * 1024, hash); } /** * Produces a deterministic stream of bytes, in randomly sized chunks, while computing the hash of the random data. * * @param length the number of bytes in the stream * @param maximumBlockSize the maximum size of any {@code ByteBuffer in the stream} * @param hash an output argument for the hash, set when the end of the stream is reached; if {@code null}, the * hash will not be computed * @return a reactive stream of bytes */ public static Flowable produceStream( final long length, final int maximumBlockSize, final AtomicReference hash ) { return Flowable.generate(new Consumer>() { final Random random = new Random(length); // Use the length as the random seed for easy reproducibility long bytesEmitted; final MessageDigest md = newMessageDigest(); @Override public void accept(final Emitter emitter) { final long remainingLength = length - bytesEmitted; if (remainingLength == 0) { emitter.onComplete(); if (hash != null) { hash.set(TextUtils.toHexString(md.digest())); } } else { final int bufferLength = (int) Math.min(remainingLength, 1 + random.nextInt(maximumBlockSize)); final byte[] bs = new byte[bufferLength]; for (int i = 0; i < bufferLength; i++) { final byte b = RANGE[(int) (random.nextDouble() * RANGE.length)]; bs[i] = b; } if (hash != null) { md.update(bs); } emitter.onNext(ByteBuffer.wrap(bs)); bytesEmitted += bufferLength; } } }); } /** * Computes the hash of the deterministic stream (as produced by {@link #produceStream(long)}). */ public static String getStreamHash(final long length) { return TextUtils.toHexString(consumeStream(produceStream(length)).blockingGet().md.digest()); } /** * Consumes the given stream and returns a data structure containing its length and digest. */ public static Single consumeStream(final Publisher publisher) { final StreamDescription seed = new StreamDescription(0, newMessageDigest()); return Flowable.fromPublisher(publisher) .reduce(seed, (desc, byteBuffer) -> { final long length = desc.length + byteBuffer.remaining(); desc.md.update(byteBuffer); return new StreamDescription(length, desc.md); }); } private static MessageDigest newMessageDigest() { try { return MessageDigest.getInstance("MD5"); } catch (final NoSuchAlgorithmException ex) { throw new AssertionError(ex); } } public static class StreamDescription { public final long length; public final MessageDigest md; public StreamDescription(final long length, final MessageDigest md) { this.length = length; this.md = md; } } } httpcore5-testing/src/main/java/org/apache/hc/core5/testing/reactive/ReactiveEchoProcessor.java0100644 0000000 0000000 00000005617 00000250600 031622 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.reactive; import java.io.IOException; import java.nio.ByteBuffer; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.impl.BasicEntityDetails; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.reactive.ReactiveRequestProcessor; import org.reactivestreams.Publisher; public final class ReactiveEchoProcessor implements ReactiveRequestProcessor { public ReactiveEchoProcessor() { } @Override public void processRequest( final HttpRequest request, final EntityDetails entityDetails, final ResponseChannel responseChannel, final HttpContext context, final Publisher requestBody, final Callback> responseBodyFuture ) throws HttpException, IOException { if (new BasicHeader(HttpHeaders.EXPECT, HeaderElements.CONTINUE).equals(request.getHeader(HttpHeaders.EXPECT))) { responseChannel.sendInformation(new BasicHttpResponse(100), context); } responseChannel.sendResponse( new BasicHttpResponse(200), new BasicEntityDetails(-1, ContentType.APPLICATION_OCTET_STREAM), context); responseBodyFuture.execute(requestBody); } }httpcore5-testing/src/main/java/org/apache/hc/core5/testing/reactive/ReactiveRandomProcessor.java0100644 0000000 0000000 00000011431 00000250600 032153 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.reactive; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.ByteBuffer; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.MethodNotSupportedException; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.impl.BasicEntityDetails; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.reactive.ReactiveRequestProcessor; import org.reactivestreams.Publisher; import io.reactivex.rxjava3.core.Flowable; public class ReactiveRandomProcessor implements ReactiveRequestProcessor { public ReactiveRandomProcessor() { } @Override public void processRequest( final HttpRequest request, final EntityDetails entityDetails, final ResponseChannel responseChannel, final HttpContext context, final Publisher requestBody, final Callback> responseBodyCallback ) throws HttpException, IOException { final String method = request.getMethod(); if (!"GET".equalsIgnoreCase(method) && !"HEAD".equalsIgnoreCase(method) && !"POST".equalsIgnoreCase(method) && !"PUT".equalsIgnoreCase(method)) { throw new MethodNotSupportedException(method + " not supported by " + getClass().getName()); } final URI uri; try { uri = request.getUri(); } catch (final URISyntaxException ex) { throw new ProtocolException(ex.getMessage(), ex); } final String path = uri.getPath(); final int slash = path.lastIndexOf('/'); if (slash != -1) { final String payload = path.substring(slash + 1); final long n; if (!payload.isEmpty()) { try { n = Long.parseLong(payload); } catch (final NumberFormatException ex) { throw new ProtocolException("Invalid request path: " + path); } } else { // random length, but make sure at least something is sent n = 1 + (int) (Math.random() * 79.0); } if (new BasicHeader(HttpHeaders.EXPECT, HeaderElements.CONTINUE).equals(request.getHeader(HttpHeaders.EXPECT))) { responseChannel.sendInformation(new BasicHttpResponse(100), context); } final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK); final Flowable stream = Reactive3TestUtils.produceStream(n); final String hash = Reactive3TestUtils.getStreamHash(n); response.addHeader("response-hash-code", hash); final BasicEntityDetails basicEntityDetails = new BasicEntityDetails(n, ContentType.APPLICATION_OCTET_STREAM); responseChannel.sendResponse(response, basicEntityDetails, context); responseBodyCallback.execute(stream); } else { throw new ProtocolException("Invalid request path: " + path); } } } httpcore5-testing/src/test/0040755 0000000 0000000 00000000000 00000250600 014724 5ustar000000000 0000000 httpcore5-testing/src/test/java/0040755 0000000 0000000 00000000000 00000250600 015645 5ustar000000000 0000000 httpcore5-testing/src/test/java/org/0040755 0000000 0000000 00000000000 00000250600 016434 5ustar000000000 0000000 httpcore5-testing/src/test/java/org/apache/0040755 0000000 0000000 00000000000 00000250600 017655 5ustar000000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/0040755 0000000 0000000 00000000000 00000250600 020247 5ustar000000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/0040755 0000000 0000000 00000000000 00000250600 021264 5ustar000000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/benchmark/0040755 0000000 0000000 00000000000 00000250600 023216 5ustar000000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/benchmark/BenchmarkToolTest.java0100644 0000000 0000000 00000015741 00000250600 027456 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.benchmark; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.concurrent.Future; import java.util.stream.Stream; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncRequestConsumer; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.AsyncServerRequestHandler; import org.apache.hc.core5.http.nio.entity.DiscardingEntityConsumer; import org.apache.hc.core5.http.nio.support.AsyncResponseBuilder; import org.apache.hc.core5.http.nio.support.BasicRequestConsumer; import org.apache.hc.core5.http.nio.support.BasicServerExchangeHandler; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2ServerBootstrap; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.net.URIBuilder; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; class BenchmarkToolTest { public static Stream protocols() { return Stream.of( Arguments.of(HttpVersionPolicy.NEGOTIATE), Arguments.of(HttpVersionPolicy.FORCE_HTTP_2) ); } private HttpAsyncServer server; private InetSocketAddress address; void setup(final HttpVersionPolicy versionPolicy) throws Exception { server = H2ServerBootstrap.bootstrap() .setRequestRouter(RequestRouter.>builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new BasicServerExchangeHandler<>( new AsyncServerRequestHandler>() { @Override public AsyncRequestConsumer> prepare( final HttpRequest request, final EntityDetails entityDetails, final HttpContext context) throws HttpException { return new BasicRequestConsumer<>(entityDetails != null ? new DiscardingEntityConsumer<>() : null); } @Override public void handle( final Message requestObject, final ResponseTrigger responseTrigger, final HttpContext context) throws HttpException, IOException { responseTrigger.submitResponse( AsyncResponseBuilder.create(HttpStatus.SC_OK) .setEntity("0123456789ABCDEF") .build(), context); } })) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()) .setVersionPolicy(versionPolicy) .create(); server.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), URIScheme.HTTP); final ListenerEndpoint listener = future.get(); address = (InetSocketAddress) listener.getAddress(); } @AfterEach void shutdown() { if (server != null) { server.close(CloseMode.IMMEDIATE); } } @ParameterizedTest(name = "{0}") @MethodSource("protocols") void testBasics(final HttpVersionPolicy versionPolicy) throws Exception { setup(versionPolicy); final BenchmarkConfig config = BenchmarkConfig.custom() .setKeepAlive(true) .setMethod(Method.POST.name()) .setPayloadText("0123456789ABCDEF") .setUri(new URIBuilder() .setScheme(URIScheme.HTTP.id) .setHost("localhost") .setPort(address .getPort()) .build()) .setConcurrencyLevel(3) .setForceHttp2(versionPolicy == HttpVersionPolicy.FORCE_HTTP_2) .setRequests(100) .build(); final HttpBenchmark httpBenchmark = new HttpBenchmark(config); final Results results = httpBenchmark.execute(); Assertions.assertNotNull(results); Assertions.assertEquals(100, results.getSuccessCount()); Assertions.assertEquals(0, results.getFailureCount()); Assertions.assertEquals(16, results.getContentLength()); Assertions.assertEquals(3, results.getConcurrencyLevel()); Assertions.assertEquals(100 * 16, results.getTotalContentBytesRecvd()); if (versionPolicy == HttpVersionPolicy.FORCE_HTTP_2) { Assertions.assertEquals(HttpVersion.HTTP_2, results.getProtocolVersion()); } } } httpcore5-testing/src/test/java/org/apache/hc/core5/benchmark/ResultFormatterTest.java0100644 0000000 0000000 00000006566 00000250600 030075 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.benchmark; import static org.hamcrest.MatcherAssert.assertThat; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.HttpVersion; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; class ResultFormatterTest { @Test void testBasics() throws Exception { final Results results = new Results( "TestServer/1.1", HttpVersion.HTTP_1_1, "localhost", 8080, "/index.html", 2924, 5, 3399, 20000, 0, 20000, 62640000, 0, 50000000); final ByteArrayOutputStream buf = new ByteArrayOutputStream(); ResultFormatter.print(new PrintStream(buf, true, StandardCharsets.US_ASCII.name()), results); assertThat(new String(buf.toByteArray(), StandardCharsets.US_ASCII).replace("\r\n", "\n"), CoreMatchers.equalTo( "Server Software:\t\tTestServer/1.1\n" + "Protocol version:\t\tHTTP/1.1\n" + "Server Hostname:\t\tlocalhost\n" + "Server Port:\t\t\t8080\n" + "Document Path:\t\t\t/index.html\n" + "Document Length:\t\t2924 bytes\n" + "\n" + "Concurrency Level:\t\t5\n" + "Time taken for tests:\t3.399000 seconds\n" + "Complete requests:\t\t20000\n" + "Failed requests:\t\t0\n" + "Kept alive:\t\t\t\t20000\n" + "Total transferred:\t\t62640000 bytes\n" + "Content transferred:\t50000000 bytes\n" + "Requests per second:\t5,884.08 [#/sec] (mean)\n" + "Time per request:\t\t0.850 [ms] (mean)\n" + "Time per request:\t\t0.170 [ms] (mean, across all concurrent requests)\n" + "Transfer rate:\t\t\t17,997.02 [Kbytes/sec] received\n" )); } } httpcore5-testing/src/test/java/org/apache/hc/core5/benchmark/RoutePoolsJmh.java0100644 0000000 0000000 00000047602 00000250600 026641 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.benchmark; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.ModalCloseable; import org.apache.hc.core5.pool.DisposalCallback; import org.apache.hc.core5.pool.LaxConnPool; import org.apache.hc.core5.pool.ManagedConnPool; import org.apache.hc.core5.pool.PoolEntry; import org.apache.hc.core5.pool.PoolReusePolicy; import org.apache.hc.core5.pool.RouteSegmentedConnPool; import org.apache.hc.core5.pool.StrictConnPool; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; /** * JMH harness that drives StrictConnPool, LaxConnPool, and RouteSegmentedConnPool * against a local HTTP/1.1 mini-cluster using real sockets and keep-alive. */ @BenchmarkMode({Mode.Throughput}) @Warmup(iterations = 10, time = 5, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 10, time = 5, timeUnit = TimeUnit.SECONDS) @Fork(1) @OutputTimeUnit(TimeUnit.SECONDS) public class RoutePoolsJmh { // --------------------------------------------------------------- // Utilities // --------------------------------------------------------------- static ThreadFactory daemonFactory(final String prefix) { final AtomicInteger n = new AtomicInteger(1); return r -> { final Thread t = new Thread(r, prefix + "-" + n.getAndIncrement()); t.setDaemon(true); return t; }; } // --------------------------------------------------------------- // Real HTTP/1.1 persistent connection used by the pool // --------------------------------------------------------------- public static final class RealConn implements ModalCloseable { private final String host; private final int port; private final int closeDelayMs; private final Socket socket; private final BufferedInputStream in; private final BufferedOutputStream out; public RealConn( final String host, final int port, final int closeDelayMs, final int soTimeoutMs, final int connectTimeoutMs) throws IOException { this.host = host; this.port = port; this.closeDelayMs = closeDelayMs; final Socket s = new Socket(); s.setTcpNoDelay(true); s.setSoTimeout(Math.max(1000, soTimeoutMs)); // read timeout s.setKeepAlive(true); s.connect(new InetSocketAddress(host, port), Math.max(1, connectTimeoutMs)); this.socket = s; this.in = new BufferedInputStream(s.getInputStream(), 32 * 1024); this.out = new BufferedOutputStream(s.getOutputStream(), 32 * 1024); } public void getOnce(final boolean keepAlive) throws IOException { final String req = "GET / HTTP/1.1\r\n" + "Host: " + host + ":" + port + "\r\n" + (keepAlive ? "Connection: keep-alive\r\n" : "Connection: close\r\n") + "\r\n"; out.write(req.getBytes(StandardCharsets.ISO_8859_1)); out.flush(); final String status = readLine(); if (status == null) { throw new IOException("No status line"); } final String[] parts = status.split(" ", 3); if (parts.length < 2 || !parts[0].startsWith("HTTP/1.")) { throw new IOException("Bad status: " + status); } final int code; try { code = Integer.parseInt(parts[1]); } catch (final NumberFormatException nfe) { throw new IOException("Bad status code in: " + status); } if (code != 200) { throw new IOException("Unexpected status: " + status); } int contentLength = -1; for (; ; ) { final String line = readLine(); if (line == null) { throw new IOException("EOF in headers"); } if (line.isEmpty()) { break; } final int colon = line.indexOf(':'); if (colon > 0) { final String name = line.substring(0, colon).trim(); if ("Content-Length".equalsIgnoreCase(name)) { try { contentLength = Integer.parseInt(line.substring(colon + 1).trim()); } catch (final NumberFormatException ignore) { // ignore } } } } if (contentLength < 0) { throw new IOException("Missing Content-Length"); } int remaining = contentLength; final byte[] buf = new byte[8192]; while (remaining > 0) { final int r = in.read(buf, 0, Math.min(buf.length, remaining)); if (r == -1) { throw new IOException("unexpected EOF in body"); } remaining -= r; } } private String readLine() throws IOException { final ByteArrayOutputStream baos = new ByteArrayOutputStream(128); for (; ; ) { final int b = in.read(); if (b == -1) { if (baos.size() == 0) { return null; } break; } if (b == '\n') { break; } baos.write(b); } final byte[] raw = baos.toByteArray(); final int len = raw.length; final int eff = (len > 0 && raw[len - 1] == '\r') ? len - 1 : len; return new String(raw, 0, eff, StandardCharsets.ISO_8859_1); } @Override public void close(final CloseMode closeMode) { if (closeDelayMs > 0) { try { Thread.sleep(closeDelayMs); } catch (final InterruptedException ie) { Thread.currentThread().interrupt(); } } try { socket.close(); } catch (final IOException ignore) { // ignore } } @Override public void close() throws IOException { if (closeDelayMs > 0) { try { Thread.sleep(closeDelayMs); } catch (final InterruptedException ie) { Thread.currentThread().interrupt(); } } socket.close(); } } // --------------------------------------------------------------- // Benchmark state & setup // --------------------------------------------------------------- @State(Scope.Benchmark) public static class BenchState { @Param({"OFFLOCK", "STRICT", "LAX"}) public String policy; @Param({"1", "4", "10", "25", "50"}) public int routes; @Param({"128"}) public int payloadBytes; @Param({"100"}) public int maxTotal; @Param({"5"}) public int defMaxPerRoute; @Param({"true"}) public boolean keepAlive; @Param({"5000"}) public int keepAliveMs; @Param({"0", "20"}) public int slowClosePct; @Param({"0", "200"}) public int closeSleepMs; @Param({"10000"}) public int soTimeoutMs; @Param({"30000"}) public int requestTimeoutMs; @Param({"1000"}) public int connectTimeoutMs; ManagedConnPool pool; DisposalCallback disposal; MiniCluster cluster; String[] routeKeys; ScheduledExecutorService maint; @Setup(Level.Trial) public void setUp() throws Exception { cluster = new MiniCluster(routes, payloadBytes); routeKeys = cluster.routeKeys(); disposal = (c, m) -> { if (c != null) { c.close(m); } }; final TimeValue ttl = TimeValue.NEG_ONE_MILLISECOND; switch (policy.toUpperCase(Locale.ROOT)) { case "STRICT": { pool = new StrictConnPool<>(defMaxPerRoute, maxTotal, ttl, PoolReusePolicy.LIFO, disposal, null); break; } case "LAX": { final LaxConnPool lax = new LaxConnPool<>(defMaxPerRoute, ttl, PoolReusePolicy.LIFO, disposal, null); lax.setMaxTotal(maxTotal); pool = lax; break; } case "OFFLOCK": { pool = new RouteSegmentedConnPool<>(defMaxPerRoute, maxTotal, ttl, PoolReusePolicy.LIFO, disposal); break; } default: { throw new IllegalArgumentException("Unknown policy: " + policy); } } // Light periodic maintenance, close idle/expired like real clients do maint = java.util.concurrent.Executors.newSingleThreadScheduledExecutor(daemonFactory("pool-maint")); maint.scheduleAtFixedRate(() -> { try { pool.closeIdle(TimeValue.ofSeconds(5)); pool.closeExpired(); } catch (final Exception ignore) { // ignore in benchmark } }, 5, 5, TimeUnit.SECONDS); } @TearDown(Level.Trial) public void tearDown() { if (pool != null) { pool.close(CloseMode.IMMEDIATE); } if (cluster != null) { cluster.close(); } if (maint != null) { maint.shutdownNow(); try { maint.awaitTermination(5, TimeUnit.SECONDS); } catch (final InterruptedException ie) { Thread.currentThread().interrupt(); } } } String pickRoute() { final int idx = ThreadLocalRandom.current().nextInt(routeKeys.length); return routeKeys[idx]; } boolean shouldDiscard() { return slowClosePct > 0 && ThreadLocalRandom.current().nextInt(100) < slowClosePct; } } // --------------------------------------------------------------- // Benchmark body // --------------------------------------------------------------- @Benchmark @Threads(50) public void lease_io_release(final BenchState s) { final String key = s.pickRoute(); final Future> f = s.pool.lease(key, null, Timeout.DISABLED, null); final PoolEntry e; try { e = f.get(s.requestTimeoutMs, TimeUnit.MILLISECONDS); } catch (final TimeoutException te) { // IMPORTANT: drop waiter on pools that queue f.cancel(true); return; } catch (final ExecutionException ee) { if (ee.getCause() instanceof TimeoutException) { f.cancel(true); } return; } catch (final InterruptedException ie) { Thread.currentThread().interrupt(); return; } if (e == null) { return; // defensive } RealConn c = e.getConnection(); if (c == null) { // parse host:port defensively final int colon = key.indexOf(':'); if (colon <= 0 || colon >= key.length() - 1) { s.pool.release(e, false); return; } final String host = key.substring(0, colon); final int port; try { port = Integer.parseInt(key.substring(colon + 1)); } catch (final NumberFormatException nfe) { s.pool.release(e, false); return; } RealConn fresh = null; try { fresh = new RealConn(host, port, s.closeSleepMs, s.soTimeoutMs, s.connectTimeoutMs); // Double-check before assigning to avoid races final RealConn existing = e.getConnection(); if (existing == null) { try { e.assignConnection(fresh); c = fresh; fresh = null; // ownership transferred } catch (final IllegalStateException already) { // someone else assigned concurrently c = e.getConnection(); if (c == null) { s.pool.release(e, false); try { fresh.close(CloseMode.IMMEDIATE); } catch (final Exception ignore) { } return; } } } else { c = existing; } } catch (final IOException ioe) { s.pool.release(e, false); if (fresh != null) { try { fresh.close(CloseMode.IMMEDIATE); } catch (final Exception ignore) { } } return; } finally { if (fresh != null) { // we created but didn't assign -> close to avoid leak try { fresh.close(CloseMode.IMMEDIATE); } catch (final Exception ignore) { } } } } if (c == null) { s.pool.release(e, false); return; } try { c.getOnce(s.keepAlive); } catch (final IOException ioe) { s.pool.release(e, false); return; } final boolean reusable = s.keepAlive && !s.shouldDiscard(); if (reusable) { e.updateExpiry(TimeValue.ofMilliseconds(s.keepAliveMs)); s.pool.release(e, true); } else { s.pool.release(e, false); } } // --------------------------------------------------------------- // Local HTTP mini-cluster // --------------------------------------------------------------- static final class MiniCluster { private final List servers = new ArrayList<>(); private final String[] keys; private final byte[] body; private final ExecutorService exec; MiniCluster(final int n, final int payloadBytes) throws IOException { this.keys = new String[n]; this.body = new byte[payloadBytes]; // Bounded, CPU-sized pool to keep the com.sun server in check final int cores = Math.max(2, Runtime.getRuntime().availableProcessors()); final int coreThreads = Math.min(64, Math.max(cores, n * 2)); final int maxThreads = Math.min(128, Math.max(coreThreads, n * 4)); this.exec = new java.util.concurrent.ThreadPoolExecutor( coreThreads, maxThreads, 60L, TimeUnit.SECONDS, new java.util.concurrent.LinkedBlockingQueue<>(2048), daemonFactory("mini-http"), new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy()); for (int i = 0; i < n; i++) { final InetSocketAddress bind = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); final HttpServer s = HttpServer.create(bind, 4096); s.createContext("/", new FixedHandler(body)); s.setExecutor(exec); s.start(); servers.add(s); keys[i] = "127.0.0.1:" + s.getAddress().getPort(); } } String[] routeKeys() { return keys; } void close() { for (final HttpServer s : servers) { try { s.stop(0); } catch (final Exception ignore) { } } exec.shutdownNow(); try { exec.awaitTermination(5, TimeUnit.SECONDS); } catch (final InterruptedException ie) { Thread.currentThread().interrupt(); } } } static final class FixedHandler implements HttpHandler { private final byte[] body; FixedHandler(final byte[] body) { this.body = body; } @Override public void handle(final HttpExchange ex) throws IOException { try (InputStream in = ex.getRequestBody()) { final byte[] buf = new byte[1024]; while (in.read(buf) != -1) { // drain } } ex.getResponseHeaders().set("Content-Type", "text/plain; charset=US-ASCII"); ex.sendResponseHeaders(200, body.length); try (OutputStream os = ex.getResponseBody()) { if (body.length > 0) { os.write(body); } os.flush(); } } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/0040755 0000000 0000000 00000000000 00000250600 022741 5ustar000000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/Result.java0100644 0000000 0000000 00000005170 00000250600 025062 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.http.message.StatusLine; public final class Result { public final HttpRequest request; public final HttpResponse response; public final T content; public final Exception exception; public enum Status { OK, NOK } public Result(final HttpRequest request, final Exception exception) { this.request = request; this.response = null; this.content = null; this.exception = exception; } public Result(final HttpRequest request, final HttpResponse response, final T content) { this.request = request; this.response = response; this.content = content; this.exception = null; } public Status getStatus() { return exception != null ? Status.NOK : Status.OK; } public boolean isOK() { return exception == null; } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append(new RequestLine(request)); buf.append(" -> "); if (exception != null) { buf.append("NOK: ").append(exception); } else { if (response != null) { buf.append("OK: ").append(new StatusLine(response)); } } return buf.toString(); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/SSLTestContexts.java0100644 0000000 0000000 00000007304 00000250600 026636 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing; import java.io.IOException; import java.net.URL; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.Provider; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import javax.net.ssl.SSLContext; import org.apache.hc.core5.ssl.SSLContextBuilder; public final class SSLTestContexts { public static SSLContext createServerSSLContext(final Provider provider, final String protocol) { final URL keyStoreURL = SSLTestContexts.class.getResource("/test.p12"); final String storePassword = "nopassword"; try { return SSLContextBuilder.create() .setProvider(provider) .setKeyStoreType("pkcs12") .loadTrustMaterial(keyStoreURL, storePassword.toCharArray()) .loadKeyMaterial(keyStoreURL, storePassword.toCharArray(), storePassword.toCharArray()) .setProtocol(protocol) .build(); } catch (final NoSuchAlgorithmException | KeyManagementException | KeyStoreException | CertificateException | UnrecoverableKeyException | IOException ex) { throw new IllegalStateException(ex); } } public static SSLContext createServerSSLContext(final String protocol) { return createServerSSLContext(null, protocol); } public static SSLContext createServerSSLContext() { return createServerSSLContext(null, null); } public static SSLContext createClientSSLContext(final Provider provider, final String protocol) { final URL keyStoreURL = SSLTestContexts.class.getResource("/test.p12"); final String storePassword = "nopassword"; try { return SSLContextBuilder.create() .setKeyStoreType("pkcs12") .loadTrustMaterial(keyStoreURL, storePassword.toCharArray()) .setProtocol(protocol) .build(); } catch (final NoSuchAlgorithmException | KeyManagementException | KeyStoreException | CertificateException | IOException ex) { throw new IllegalStateException(ex); } } public static SSLContext createClientSSLContext(final String protocol) { return createClientSSLContext(null, protocol); } public static SSLContext createClientSSLContext() { return createClientSSLContext(null, null); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/0040755 0000000 0000000 00000000000 00000250600 024362 5ustar000000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicAuthenticationTest.java0100644 0000000 0000000 00000024611 00000250600 032347 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import static org.hamcrest.MatcherAssert.assertThat; import java.nio.charset.StandardCharsets; import java.util.Random; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.impl.bootstrap.HttpRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpServer; import org.apache.hc.core5.http.impl.bootstrap.StandardFilter; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.entity.ByteArrayEntity; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.io.support.AbstractHttpServerAuthFilter; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.extension.classic.HttpRequesterResource; import org.apache.hc.core5.testing.extension.classic.HttpServerResource; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; abstract class ClassicAuthenticationTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @RegisterExtension private HttpServerResource serverResource; @RegisterExtension private HttpRequesterResource clientResource; public ClassicAuthenticationTest(final Boolean respondImmediately) { this.serverResource = new HttpServerResource(); this.serverResource.configure(bootstrap -> bootstrap .setSocketConfig( SocketConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setRequestRouter(RequestRouter.builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", new EchoHandler()) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()) .replaceFilter(StandardFilter.EXPECT_CONTINUE.name(), new AbstractHttpServerAuthFilter(respondImmediately) { @Override protected String parseChallengeResponse( final String challenge, final HttpContext context) throws HttpException { return challenge; } @Override protected boolean authenticate( final String challengeResponse, final URIAuthority authority, final String requestUri, final HttpContext context) { return challengeResponse != null && challengeResponse.equals("let me pass"); } @Override protected String generateChallenge( final String challengeResponse, final URIAuthority authority, final String requestUri, final HttpContext context) { return "who goes there?"; } @Override protected HttpEntity generateResponseContent(final HttpResponse unauthorized) { return new StringEntity("You shall not pass!!!"); } }) ); this.clientResource = new HttpRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setSslContext(SSLTestContexts.createClientSSLContext()) .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .build()) ); } @Test void testGetRequestAuthentication() throws Exception { final HttpServer server = serverResource.start(); final HttpRequester requester = clientResource.start(); final HttpHost target = new HttpHost("localhost", server.getLocalPort()); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.GET, "/stuff"); try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) { assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_UNAUTHORIZED)); final String body1 = EntityUtils.toString(response1.getEntity()); assertThat(body1, CoreMatchers.equalTo("You shall not pass!!!")); } final ClassicHttpRequest request2 = new BasicClassicHttpRequest(Method.GET, "/stuff"); request2.setHeader(HttpHeaders.AUTHORIZATION, "let me pass"); try (final ClassicHttpResponse response2 = requester.execute(target, request2, TIMEOUT, context)) { assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body1 = EntityUtils.toString(response2.getEntity()); assertThat(body1, CoreMatchers.equalTo("")); } } @Test void testPostRequestAuthentication() throws Exception { final HttpServer server = serverResource.start(); final HttpRequester requester = clientResource.start(); final HttpHost target = new HttpHost("localhost", server.getLocalPort()); final HttpCoreContext context = HttpCoreContext.create(); final Random rnd = new Random(); final byte[] stuff = new byte[10240]; for (int i = 0; i < stuff.length; i++) { stuff[i] = (byte) ('a' + rnd.nextInt(10)); } final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff"); request1.setEntity(new ByteArrayEntity(stuff, ContentType.TEXT_PLAIN)); try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) { assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_UNAUTHORIZED)); final String body1 = EntityUtils.toString(response1.getEntity()); assertThat(body1, CoreMatchers.equalTo("You shall not pass!!!")); } final ClassicHttpRequest request2 = new BasicClassicHttpRequest(Method.POST, "/stuff"); request2.setHeader(HttpHeaders.AUTHORIZATION, "let me pass"); request2.setEntity(new ByteArrayEntity(stuff, ContentType.TEXT_PLAIN)); try (final ClassicHttpResponse response2 = requester.execute(target, request2, TIMEOUT, context)) { assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body1 = EntityUtils.toString(response2.getEntity()); assertThat(body1, CoreMatchers.equalTo(new String(stuff, StandardCharsets.US_ASCII))); } } @Test void testPostRequestAuthenticationNoExpectContinue() throws Exception { final HttpServer server = serverResource.start(); final HttpRequester requester = clientResource.start(); final HttpHost target = new HttpHost("localhost", server.getLocalPort()); final HttpCoreContext context = HttpCoreContext.create(); final Random rnd = new Random(); final byte[] stuff = new byte[10240]; for (int i = 0; i < stuff.length; i++) { stuff[i] = (byte) ('a' + rnd.nextInt(10)); } final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff"); request1.setVersion(HttpVersion.HTTP_1_0); request1.setEntity(new ByteArrayEntity(stuff, ContentType.TEXT_PLAIN)); try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) { assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_UNAUTHORIZED)); final String body1 = EntityUtils.toString(response1.getEntity()); assertThat(body1, CoreMatchers.equalTo("You shall not pass!!!")); } final ClassicHttpRequest request2 = new BasicClassicHttpRequest(Method.POST, "/stuff"); request2.setHeader(HttpHeaders.AUTHORIZATION, "let me pass"); request2.setVersion(HttpVersion.HTTP_1_0); request2.setEntity(new ByteArrayEntity(stuff, ContentType.TEXT_PLAIN)); try (final ClassicHttpResponse response2 = requester.execute(target, request2, TIMEOUT, context)) { assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body1 = EntityUtils.toString(response2.getEntity()); assertThat(body1, CoreMatchers.equalTo(new String(stuff, StandardCharsets.US_ASCII))); } } } ././@LongLink0100644 0000000 0000000 00000000147 00000250600 011613 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicHttp1CoreTransportTest.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicHttp1CoreTransportTest.ja0100644 0000000 0000000 00000011442 00000250600 032565 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import java.io.IOException; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpServer; import org.apache.hc.core5.http.impl.bootstrap.StandardFilter; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.io.HttpFilterChain; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.extension.classic.HttpRequesterResource; import org.apache.hc.core5.testing.extension.classic.HttpServerResource; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.extension.RegisterExtension; abstract class ClassicHttp1CoreTransportTest extends ClassicHttpCoreTransportTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @RegisterExtension private final HttpServerResource serverResource; @RegisterExtension private final HttpRequesterResource clientResource; public ClassicHttp1CoreTransportTest(final URIScheme scheme) { super(scheme); this.serverResource = new HttpServerResource(); this.serverResource.configure(bootstrap -> bootstrap .setSslContext(scheme == URIScheme.HTTPS ? SSLTestContexts.createServerSSLContext() : null) .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setRequestRouter(RequestRouter.builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", new EchoHandler()) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()) .addFilterBefore(StandardFilter.MAIN_HANDLER.name(), "no-keep-alive", (request, responseTrigger, context, chain) -> chain.proceed(request, new HttpFilterChain.ResponseTrigger() { @Override public void sendInformation( final ClassicHttpResponse response) throws HttpException, IOException { responseTrigger.sendInformation(response); } @Override public void submitResponse( final ClassicHttpResponse response) throws HttpException, IOException { if (request.getPath().startsWith("/no-keep-alive")) { response.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); } responseTrigger.submitResponse(response); } }, context))); this.clientResource = new HttpRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setSslContext(SSLTestContexts.createClientSSLContext()) .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .build())); } @Override HttpServer serverStart() throws IOException { return serverResource.start(); } @Override HttpRequester clientStart() throws IOException { return clientResource.start(); } } ././@LongLink0100644 0000000 0000000 00000000146 00000250600 011612 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicHttpCoreTransportTest.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicHttpCoreTransportTest.jav0100644 0000000 0000000 00000021400 00000250600 032665 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import static org.hamcrest.MatcherAssert.assertThat; import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpServer; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; abstract class ClassicHttpCoreTransportTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); private final URIScheme scheme; public ClassicHttpCoreTransportTest(final URIScheme scheme) { this.scheme = scheme; } abstract HttpServer serverStart() throws IOException; abstract HttpRequester clientStart() throws IOException; @Test void testSequentialRequests() throws Exception { final HttpServer server = serverStart(); final HttpRequester requester = clientStart(); final HttpHost target = new HttpHost(scheme.id, "localhost", server.getLocalPort()); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff"); request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN)); try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) { assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body1 = EntityUtils.toString(response1.getEntity()); assertThat(body1, CoreMatchers.equalTo("some stuff")); } final ClassicHttpRequest request2 = new BasicClassicHttpRequest(Method.POST, "/other-stuff"); request2.setEntity(new StringEntity("some other stuff", ContentType.TEXT_PLAIN)); try (final ClassicHttpResponse response2 = requester.execute(target, request2, TIMEOUT, context)) { assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body2 = EntityUtils.toString(response2.getEntity()); assertThat(body2, CoreMatchers.equalTo("some other stuff")); } final ClassicHttpRequest request3 = new BasicClassicHttpRequest(Method.POST, "/more-stuff"); request3.setEntity(new StringEntity("some more stuff", ContentType.TEXT_PLAIN)); try (final ClassicHttpResponse response3 = requester.execute(target, request3, TIMEOUT, context)) { assertThat(response3.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body3 = EntityUtils.toString(response3.getEntity()); assertThat(body3, CoreMatchers.equalTo("some more stuff")); } } @Test void testSequentialRequestsNonPersistentConnection() throws Exception { final HttpServer server = serverStart(); final HttpRequester requester = clientStart(); final HttpHost target = new HttpHost(scheme.id, "localhost", server.getLocalPort()); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/no-keep-alive/stuff"); request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN)); try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) { assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body1 = EntityUtils.toString(response1.getEntity()); assertThat(body1, CoreMatchers.equalTo("some stuff")); } final ClassicHttpRequest request2 = new BasicClassicHttpRequest(Method.POST, "/no-keep-alive/other-stuff"); request2.setEntity(new StringEntity("some other stuff", ContentType.TEXT_PLAIN)); try (final ClassicHttpResponse response2 = requester.execute(target, request2, TIMEOUT, context)) { assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body2 = EntityUtils.toString(response2.getEntity()); assertThat(body2, CoreMatchers.equalTo("some other stuff")); } final ClassicHttpRequest request3 = new BasicClassicHttpRequest(Method.POST, "/no-keep-alive/more-stuff"); request3.setEntity(new StringEntity("some more stuff", ContentType.TEXT_PLAIN)); try (final ClassicHttpResponse response3 = requester.execute(target, request3, TIMEOUT, context)) { assertThat(response3.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body3 = EntityUtils.toString(response3.getEntity()); assertThat(body3, CoreMatchers.equalTo("some more stuff")); } } @Test void testMultiThreadedRequests() throws Exception { final HttpServer server = serverStart(); final HttpRequester requester = clientStart(); final int c = 10; final CountDownLatch latch = new CountDownLatch(c); final AtomicLong n = new AtomicLong(c + 100); final AtomicReference exRef = new AtomicReference<>(); final ExecutorService executorService = Executors.newFixedThreadPool(c); try { final HttpHost target = new HttpHost(scheme.id, "localhost", server.getLocalPort()); for (int i = 0; i < c; i++) { executorService.execute(() -> { try { while (n.decrementAndGet() > 0) { try { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff"); request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN)); requester.execute(target, request1, TIMEOUT, context, response -> { Assertions.assertEquals(HttpStatus.SC_OK, response.getCode()); Assertions.assertEquals("some stuff", EntityUtils.toString(response.getEntity())); return null; }); } catch (final Exception ex) { Assertions.fail(ex); } } } catch (final AssertionError ex) { exRef.compareAndSet(null, ex); } finally { latch.countDown(); } }); } Assertions.assertTrue(latch.await(5, TimeUnit.MINUTES)); } finally { executorService.shutdownNow(); } final AssertionError assertionError = exRef.get(); if (assertionError != null) { throw assertionError; } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicIntegrationTest.java0100644 0000000 0000000 00000105647 00000250600 031664 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketTimeoutException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.HttpProcessors; import org.apache.hc.core5.http.impl.io.DefaultBHttpClientConnectionFactory; import org.apache.hc.core5.http.impl.io.HttpRequestExecutor; import org.apache.hc.core5.http.io.HttpClientConnection; import org.apache.hc.core5.http.io.HttpConnectionFactory; import org.apache.hc.core5.http.io.entity.AbstractHttpEntity; import org.apache.hc.core5.http.io.entity.ByteArrayEntity; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.io.support.BasicHttpServerExpectationDecorator; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.http.protocol.DefaultHttpProcessor; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http.protocol.RequestConnControl; import org.apache.hc.core5.http.protocol.RequestContent; import org.apache.hc.core5.http.protocol.RequestExpectContinue; import org.apache.hc.core5.http.protocol.RequestTE; import org.apache.hc.core5.http.protocol.RequestTargetHost; import org.apache.hc.core5.http.protocol.RequestUserAgent; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.extension.classic.ClassicTestResources; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import javax.net.ssl.SSLSocket; abstract class ClassicIntegrationTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); private final URIScheme scheme; @RegisterExtension private final ClassicTestResources testResources; public ClassicIntegrationTest(final URIScheme scheme) { this.scheme = scheme; this.testResources = new ClassicTestResources(scheme, TIMEOUT); } /** * This test case executes a series of simple GET requests */ @Test void testSimpleBasicHttpRequests() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); final int reqNo = 20; final Random rnd = new Random(); // Prepare some random data final List testData = new ArrayList<>(reqNo); for (int i = 0; i < reqNo; i++) { final int size = rnd.nextInt(5000); final byte[] data = new byte[size]; rnd.nextBytes(data); testData.add(data); } // Initialize the server-side request handler server.register("*", (request, response, context) -> { String s = request.getPath(); if (s.startsWith("/?")) { s = s.substring(2); } final int index = Integer.parseInt(s); final byte[] data = testData.get(index); final ByteArrayEntity entity = new ByteArrayEntity(data, null); response.setEntity(entity); }); server.start(); client.start(); final HttpCoreContext context = HttpCoreContext.create(); final HttpHost host = new HttpHost(scheme.id, "localhost", server.getPort()); for (int r = 0; r < reqNo; r++) { final BasicClassicHttpRequest get = new BasicClassicHttpRequest(Method.GET, "/?" + r); try (final ClassicHttpResponse response = client.execute(host, get, context)) { final byte[] received = EntityUtils.toByteArray(response.getEntity()); final byte[] expected = testData.get(r); Assertions.assertEquals(expected.length, received.length); for (int i = 0; i < expected.length; i++) { Assertions.assertEquals(expected[i], received[i]); } } } } /** * This test case executes a series of simple POST requests with content length * delimited content. */ @Test void testSimpleHttpPostsWithContentLength() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); final int reqNo = 20; final Random rnd = new Random(); // Prepare some random data final List testData = new ArrayList<>(reqNo); for (int i = 0; i < reqNo; i++) { final int size = rnd.nextInt(5000); final byte[] data = new byte[size]; rnd.nextBytes(data); testData.add(data); } // Initialize the server-side request handler server.register("*", (request, response, context) -> { final HttpEntity entity = request.getEntity(); if (entity != null) { final byte[] data = EntityUtils.toByteArray(entity); response.setEntity(new ByteArrayEntity(data, null)); } }); server.start(); client.start(); final HttpCoreContext context = HttpCoreContext.create(); final HttpHost host = new HttpHost(scheme.id, "localhost", server.getPort()); for (int r = 0; r < reqNo; r++) { final BasicClassicHttpRequest post = new BasicClassicHttpRequest(Method.POST, "/"); final byte[] data = testData.get(r); post.setEntity(new ByteArrayEntity(data, null)); try (final ClassicHttpResponse response = client.execute(host, post, context)) { final byte[] received = EntityUtils.toByteArray(response.getEntity()); final byte[] expected = testData.get(r); Assertions.assertEquals(expected.length, received.length); for (int i = 0; i < expected.length; i++) { Assertions.assertEquals(expected[i], received[i]); } } } } /** * This test case executes a series of simple POST requests with chunk * coded content content. */ @Test void testSimpleHttpPostsChunked() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); final int reqNo = 20; final Random rnd = new Random(); // Prepare some random data final List testData = new ArrayList<>(reqNo); for (int i = 0; i < reqNo; i++) { final int size = rnd.nextInt(20000); final byte[] data = new byte[size]; rnd.nextBytes(data); testData.add(data); } // Initialize the server-side request handler server.register("*", (request, response, context) -> { final HttpEntity entity = request.getEntity(); if (entity != null) { final byte[] data = EntityUtils.toByteArray(entity); response.setEntity(new ByteArrayEntity(data, null, true)); } }); server.start(); client.start(); final HttpCoreContext context = HttpCoreContext.create(); final HttpHost host = new HttpHost(scheme.id, "localhost", server.getPort()); for (int r = 0; r < reqNo; r++) { final BasicClassicHttpRequest post = new BasicClassicHttpRequest(Method.POST, "/"); final byte[] data = testData.get(r); post.setEntity(new ByteArrayEntity(data, null, true)); try (final ClassicHttpResponse response = client.execute(host, post, context)) { final byte[] received = EntityUtils.toByteArray(response.getEntity()); final byte[] expected = testData.get(r); Assertions.assertEquals(expected.length, received.length); for (int i = 0; i < expected.length; i++) { Assertions.assertEquals(expected[i], received[i]); } } } } /** * This test case executes a series of simple HTTP/1.0 POST requests. */ @Test void testSimpleHttpPostsHTTP10() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); final int reqNo = 20; final Random rnd = new Random(); // Prepare some random data final List testData = new ArrayList<>(reqNo); for (int i = 0; i < reqNo; i++) { final int size = rnd.nextInt(5000); final byte[] data = new byte[size]; rnd.nextBytes(data); testData.add(data); } // Initialize the server-side request handler server.register("*", (request, response, context) -> { final HttpEntity entity = request.getEntity(); if (entity != null) { final byte[] data = EntityUtils.toByteArray(entity); response.setEntity(new ByteArrayEntity(data, null)); } }); server.start(); client.start(); final HttpCoreContext context = HttpCoreContext.create(); final HttpHost host = new HttpHost(scheme.id, "localhost", server.getPort()); for (int r = 0; r < reqNo; r++) { // Set protocol level to HTTP/1.0 final BasicClassicHttpRequest post = new BasicClassicHttpRequest(Method.POST, "/"); post.setVersion(HttpVersion.HTTP_1_0); final byte[] data = testData.get(r); post.setEntity(new ByteArrayEntity(data, null)); try (final ClassicHttpResponse response = client.execute(host, post, context)) { final byte[] received = EntityUtils.toByteArray(response.getEntity()); final byte[] expected = testData.get(r); Assertions.assertEquals(expected.length, received.length); for (int i = 0; i < expected.length; i++) { Assertions.assertEquals(expected[i], received[i]); } } } } /** * This test case ensures that HTTP/1.1 features are disabled when executing * HTTP/1.0 compatible requests. */ @Test void testHTTP11FeaturesDisabledWithHTTP10Requests() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); server.start(); client.start(); final HttpCoreContext context = HttpCoreContext.create(); final HttpHost host = new HttpHost(scheme.id, "localhost", server.getPort()); final BasicClassicHttpRequest post = new BasicClassicHttpRequest(Method.POST, "/"); post.setVersion(HttpVersion.HTTP_1_0); post.setEntity(new ByteArrayEntity(new byte[] {'a', 'b', 'c'}, null, true)); Assertions.assertThrows(ProtocolException.class, () -> { try (final ClassicHttpResponse response = client.execute(host, post, context)) { EntityUtils.consume(response.getEntity()); } }); } /** * This test case executes a series of simple POST requests using * the 'expect: continue' handshake. */ @Test void testHttpPostsWithExpectContinue() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); final int reqNo = 20; final Random rnd = new Random(); // Prepare some random data final List testData = new ArrayList<>(reqNo); for (int i = 0; i < reqNo; i++) { final int size = rnd.nextInt(5000); final byte[] data = new byte[size]; rnd.nextBytes(data); testData.add(data); } // Initialize the server-side request handler server.register("*", (request, response, context) -> { final HttpEntity entity = request.getEntity(); if (entity != null) { final byte[] data = EntityUtils.toByteArray(entity); response.setEntity(new ByteArrayEntity(data, null, true)); } }); server.start(); client.start(); final HttpCoreContext context = HttpCoreContext.create(); final HttpHost host = new HttpHost(scheme.id, "localhost", server.getPort()); for (int r = 0; r < reqNo; r++) { final BasicClassicHttpRequest post = new BasicClassicHttpRequest(Method.POST, "/"); final byte[] data = testData.get(r); post.setEntity(new ByteArrayEntity(data, null, true)); try (final ClassicHttpResponse response = client.execute(host, post, context)) { final byte[] received = EntityUtils.toByteArray(response.getEntity()); final byte[] expected = testData.get(r); Assertions.assertEquals(expected.length, received.length); for (int i = 0; i < expected.length; i++) { Assertions.assertEquals(expected[i], received[i]); } } } } /** * This test case executes a series of simple POST requests that do not * meet the target server expectations. */ @Test void testHttpPostsWithExpectationVerification() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); final int reqNo = 20; // Initialize the server-side request handler server.register("*", (request, response, context) -> response.setEntity(new StringEntity("No content"))); server.configure(handler -> new BasicHttpServerExpectationDecorator(handler) { @Override protected ClassicHttpResponse verify(final ClassicHttpRequest request, final HttpContext context) { final Header someheader = request.getFirstHeader("Secret"); if (someheader != null) { final int secretNumber; try { secretNumber = Integer.parseInt(someheader.getValue()); } catch (final NumberFormatException ex) { final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_BAD_REQUEST); response.setEntity(new StringEntity(ex.toString())); return response; } if (secretNumber >= 2) { final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_EXPECTATION_FAILED); response.setEntity(new StringEntity("Wrong secret number", ContentType.TEXT_PLAIN)); return response; } } return null; } }); server.start(); client.start(); final HttpCoreContext context = HttpCoreContext.create(); final HttpHost host = new HttpHost(scheme.id, "localhost", server.getPort()); for (int r = 0; r < reqNo; r++) { final BasicClassicHttpRequest post = new BasicClassicHttpRequest(Method.POST, "/"); post.addHeader("Secret", Integer.toString(r)); final byte[] b = new byte[2048]; for (int i = 0; i < b.length; i++) { b[i] = (byte) ('a' + r); } post.setEntity(new ByteArrayEntity(b, ContentType.TEXT_PLAIN)); try (final ClassicHttpResponse response = client.execute(host, post, context)) { final HttpEntity responseEntity = response.getEntity(); Assertions.assertNotNull(responseEntity); EntityUtils.consume(responseEntity); if (r >= 2) { Assertions.assertEquals(HttpStatus.SC_EXPECTATION_FAILED, response.getCode()); } else { Assertions.assertEquals(HttpStatus.SC_OK, response.getCode()); } } } } static class RepeatingEntity extends AbstractHttpEntity { private final byte[] raw; private final int n; public RepeatingEntity(final String content, final Charset charset, final int n, final boolean chunked) { super(ContentType.TEXT_PLAIN.withCharset(charset), null, chunked); final Charset cs = charset != null ? charset : StandardCharsets.UTF_8; this.raw = content.getBytes(cs); this.n = n; } @Override public InputStream getContent() throws IOException, IllegalStateException { throw new IllegalStateException("This method is not implemented"); } @Override public long getContentLength() { return (this.raw.length + 2) * this.n; } @Override public boolean isRepeatable() { return true; } @Override public boolean isStreaming() { return false; } @Override public void writeTo(final OutputStream outStream) throws IOException { for (int i = 0; i < this.n; i++) { outStream.write(this.raw); outStream.write('\r'); outStream.write('\n'); } outStream.flush(); } @Override public void close() throws IOException { } } @Test void testHttpContent() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); final String[] patterns = { "0123456789ABCDEF", "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" + "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" + "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" + "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" + "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" + "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" + "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" + "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" + "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" + "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" + "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" + "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" + "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" + "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" + "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that" }; // Initialize the server-side request handler server.register("*", (request, response, context) -> { int n = 1; String s = request.getPath(); if (s.startsWith("/?n=")) { s = s.substring(4); try { n = Integer.parseInt(s); if (n <= 0) { throw new HttpException("Invalid request: " + "number of repetitions cannot be negative or zero"); } } catch (final NumberFormatException ex) { throw new HttpException("Invalid request: " + "number of repetitions is invalid"); } } final HttpEntity entity = request.getEntity(); if (entity != null) { final String line = EntityUtils.toString(entity); final ContentType contentType = ContentType.parse(entity.getContentType()); final Charset charset = ContentType.getCharset(contentType, StandardCharsets.UTF_8); response.setEntity(new RepeatingEntity(line, charset, n, n % 2 == 0)); } }); server.start(); client.start(); final HttpCoreContext context = HttpCoreContext.create(); final HttpHost host = new HttpHost(scheme.id, "localhost", server.getPort()); for (final String pattern : patterns) { for (int n = 1000; n < 1020; n++) { final BasicClassicHttpRequest post = new BasicClassicHttpRequest( Method.POST.name(), "/?n=" + n); post.setEntity(new StringEntity(pattern, ContentType.TEXT_PLAIN, n % 2 == 0)); try (final ClassicHttpResponse response = client.execute(host, post, context)) { final HttpEntity entity = response.getEntity(); Assertions.assertNotNull(entity); final InputStream inStream = entity.getContent(); final ContentType contentType = ContentType.parse(entity.getContentType()); final Charset charset = ContentType.getCharset(contentType, StandardCharsets.UTF_8); Assertions.assertNotNull(inStream); final BufferedReader reader = new BufferedReader(new InputStreamReader(inStream, charset)); String line; int count = 0; while ((line = reader.readLine()) != null) { Assertions.assertEquals(pattern, line); count++; } Assertions.assertEquals(n, count); } } } } @Test void testHttpPostNoEntity() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); server.register("*", (request, response, context) -> { final HttpEntity entity = request.getEntity(); if (entity != null) { final byte[] data = EntityUtils.toByteArray(entity); response.setEntity(new ByteArrayEntity(data, null)); } }); server.start(); client.start(); final HttpCoreContext context = HttpCoreContext.create(); final HttpHost host = new HttpHost(scheme.id, "localhost", server.getPort()); final BasicClassicHttpRequest post = new BasicClassicHttpRequest(Method.POST, "/"); post.setEntity(null); try (final ClassicHttpResponse response = client.execute(host, post, context)) { Assertions.assertEquals(HttpStatus.SC_OK, response.getCode()); final byte[] received = EntityUtils.toByteArray(response.getEntity()); Assertions.assertEquals(0, received.length); } } @Test void testHttpPostNoContentLength() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); server.register("*", (request, response, context) -> { final HttpEntity entity = request.getEntity(); if (entity != null) { final byte[] data = EntityUtils.toByteArray(entity); response.setEntity(new ByteArrayEntity(data, null)); } }); server.start(); client.configure(new DefaultHttpProcessor( RequestTargetHost.INSTANCE, RequestConnControl.INSTANCE, RequestUserAgent.INSTANCE, RequestExpectContinue.INSTANCE, RequestTE.INSTANCE)); client.start(); final HttpCoreContext context = HttpCoreContext.create(); final HttpHost host = new HttpHost(scheme.id, "localhost", server.getPort()); final BasicClassicHttpRequest post = new BasicClassicHttpRequest(Method.POST, "/"); post.setEntity(null); try (final ClassicHttpResponse response = client.execute(host, post, context)) { Assertions.assertEquals(HttpStatus.SC_OK, response.getCode()); final byte[] received = EntityUtils.toByteArray(response.getEntity()); Assertions.assertEquals(0, received.length); } } @Test void testHttpPostIdentity() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); server.register("*", (request, response, context) -> { final HttpEntity entity = request.getEntity(); if (entity != null) { final byte[] data = EntityUtils.toByteArray(entity); response.setEntity(new ByteArrayEntity(data, null)); } }); server.start(); client.configure(new DefaultHttpProcessor( (request, entity, context) -> request.addHeader(HttpHeaders.TRANSFER_ENCODING, "identity"), RequestTargetHost.INSTANCE, RequestConnControl.INSTANCE, RequestUserAgent.INSTANCE, RequestExpectContinue.INSTANCE)); client.start(); final HttpCoreContext context = HttpCoreContext.create(); final HttpHost host = new HttpHost(scheme.id, "localhost", server.getPort()); final BasicClassicHttpRequest post = new BasicClassicHttpRequest(Method.POST, "/"); post.setEntity(null); try (final ClassicHttpResponse response = client.execute(host, post, context)) { Assertions.assertEquals(HttpStatus.SC_NOT_IMPLEMENTED, response.getCode()); } } @Test void testNoContentResponse() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); final int reqNo = 20; // Initialize the server-side request handler server.register("*", (request, response, context) -> response.setCode(HttpStatus.SC_NO_CONTENT)); server.start(); client.start(); final HttpCoreContext context = HttpCoreContext.create(); final HttpHost host = new HttpHost(scheme.id, "localhost", server.getPort()); for (int r = 0; r < reqNo; r++) { final BasicClassicHttpRequest get = new BasicClassicHttpRequest(Method.GET, "/?" + r); try (final ClassicHttpResponse response = client.execute(host, get, context)) { Assertions.assertNull(response.getEntity()); } } } @Test void testHeaderTooLarge() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); server.register("*", (request, response, context) -> response.setEntity(new StringEntity("All is well", StandardCharsets.US_ASCII))); server.configure( Http1Config.custom() .setMaxLineLength(100) .build()); server.start(); client.start(); final HttpCoreContext context = HttpCoreContext.create(); final HttpHost host = new HttpHost(scheme.id, "localhost", server.getPort()); final BasicClassicHttpRequest get1 = new BasicClassicHttpRequest(Method.GET, "/"); get1.setHeader("big-f-header", "1234567890123456789012345678901234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890"); try (final ClassicHttpResponse response1 = client.execute(host, get1, context)) { Assertions.assertEquals(431, response1.getCode()); EntityUtils.consume(response1.getEntity()); } } @Test void testHeaderTooLargePost() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); server.register("*", (request, response, context) -> response.setEntity(new StringEntity("All is well", StandardCharsets.US_ASCII))); server.configure( Http1Config.custom() .setMaxLineLength(100) .build()); server.start(); client.configure( new DefaultHttpProcessor(RequestContent.INSTANCE, RequestTargetHost.INSTANCE, RequestConnControl.INSTANCE)); client.start(); final HttpCoreContext context = HttpCoreContext.create(); final HttpHost host = new HttpHost(scheme.id, "localhost", server.getPort()); final ClassicHttpRequest post1 = new BasicClassicHttpRequest(Method.POST, "/"); post1.setHeader("big-f-header", "1234567890123456789012345678901234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890"); final byte[] b = new byte[2048]; for (int i = 0; i < b.length; i++) { b[i] = (byte) ('a' + i % 10); } post1.setEntity(new ByteArrayEntity(b, ContentType.TEXT_PLAIN)); try (final ClassicHttpResponse response1 = client.execute(host, post1, context)) { Assertions.assertEquals(431, response1.getCode()); EntityUtils.consume(response1.getEntity()); } } @Test void testImmediateCloseUponSocketTimeout() throws Exception { final int socketTimeoutMillis = 1000; final int serverDelayMillis = 5 * socketTimeoutMillis; final ClassicTestServer server = testResources.server(); final CountDownLatch serverDelayStarted = new CountDownLatch(1); // Configure server to delay significantly before responding server.register("*", (request, response, context) -> { serverDelayStarted.countDown(); try { // Delay much longer than socket timeout Thread.sleep(serverDelayMillis); } catch (final InterruptedException ex) { Thread.currentThread().interrupt(); } response.setCode(HttpStatus.SC_OK); response.setEntity(new StringEntity("Delayed response", ContentType.TEXT_PLAIN)); }); server.start(); final HttpHost host = new HttpHost(scheme.id, "localhost", server.getPort()); final HttpCoreContext context = HttpCoreContext.create(); final HttpConnectionFactory connFactory = DefaultBHttpClientConnectionFactory.builder().build(); final HttpRequestExecutor requestExecutor = new HttpRequestExecutor(); final HttpProcessor processor = HttpProcessors.client(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.setAuthority(new URIAuthority(host)); request.setScheme(host.getSchemeName()); runWithSocket(host, socketTimeoutMillis, socket -> { try { final HttpClientConnection connection = connFactory.createConnection(socket); requestExecutor.preProcess(request, processor, context); final long startTime = System.currentTimeMillis(); try (final ClassicHttpResponse response = requestExecutor.execute( request, connection, context)) { Assertions.fail("Expected SocketTimeoutException not thrown"); } catch (final SocketTimeoutException e) { // Expected due to server delay exceeding socket timeout } final long endTime = System.currentTimeMillis(); final long durationMillis = endTime - startTime; // Assert that the timeout occurred around the socket timeout duration Assertions.assertTrue(durationMillis >= socketTimeoutMillis && durationMillis < socketTimeoutMillis * 2, String.format("Socket timeout should occur around %dms (took %dms)", socketTimeoutMillis, durationMillis)); } catch (final IOException | HttpException e) { Assertions.fail("IOException during request execution: " + e.getMessage()); } }); } private static void runWithSocket( final HttpHost host, final int socketTimeoutMillis, final Consumer socketConsumer) throws IOException { try (final Socket clientSocket = new Socket()) { clientSocket.setSoTimeout(socketTimeoutMillis); clientSocket.connect(new InetSocketAddress(host.getHostName(), host.getPort())); if (host.getSchemeName().equalsIgnoreCase("http")) { socketConsumer.accept(clientSocket); return; } try (final SSLSocket sslSocket = (SSLSocket) SSLTestContexts.createClientSSLContext() .getSocketFactory() .createSocket(clientSocket, host.getHostName(), -1, true)) { sslSocket.startHandshake(); socketConsumer.accept(sslSocket); } } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicIntegrationTests.java0100644 0000000 0000000 00000005674 00000250600 032046 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import org.apache.hc.core5.http.URIScheme; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; class ClassicIntegrationTests { @Nested @DisplayName("Core transport") class CoreTransport extends ClassicHttp1CoreTransportTest { public CoreTransport() { super(URIScheme.HTTP); } } @Nested @DisplayName("Core transport (TLS)") class CoreTransportTls extends ClassicHttp1CoreTransportTest { public CoreTransportTls() { super(URIScheme.HTTPS); } } @Nested @DisplayName("Authentication") class Authentication extends ClassicAuthenticationTest { public Authentication() { super(false); } } @Nested @DisplayName("Authentication (immediate response)") class AuthenticationImmediateResponse extends ClassicAuthenticationTest { public AuthenticationImmediateResponse() { super(true); } } @Nested @DisplayName("Out-of-order response monitoring") class MonitoringResponseOutOfOrderStrategy extends MonitoringResponseOutOfOrderStrategyIntegrationTest { public MonitoringResponseOutOfOrderStrategy() { super(URIScheme.HTTP); } } @Nested @DisplayName("Out-of-order response monitoring (TLS)") class MonitoringResponseOutOfOrderStrategyTls extends MonitoringResponseOutOfOrderStrategyIntegrationTest { public MonitoringResponseOutOfOrderStrategyTls() { super(URIScheme.HTTPS); } } @Nested @DisplayName("Server filters") class HttpFilters extends ClassicServerBootstrapFilterTest { public HttpFilters() { super(URIScheme.HTTP); } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicProtocolTests.java0100644 0000000 0000000 00000003341 00000250600 031351 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import org.apache.hc.core5.http.URIScheme; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; class ClassicProtocolTests { @Nested @DisplayName("Classic HTTP/1.1 (plain)") class Http1 extends ClassicIntegrationTest { public Http1() { super(URIScheme.HTTP); } } @Nested @DisplayName("Classic HTTP/1.1 (TLS)") class Http1Tls extends ClassicIntegrationTest { public Http1Tls() { super(URIScheme.HTTPS); } } } ././@LongLink0100644 0000000 0000000 00000000152 00000250600 011607 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicServerBootstrapFilterTest.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicServerBootstrapFilterTest0100644 0000000 0000000 00000013265 00000250600 032765 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import static org.hamcrest.MatcherAssert.assertThat; import java.io.IOException; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpServer; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.io.HttpFilterChain; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.extension.classic.HttpRequesterResource; import org.apache.hc.core5.testing.extension.classic.HttpServerResource; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; abstract class ClassicServerBootstrapFilterTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); private final URIScheme scheme; @RegisterExtension private final HttpServerResource serverResource; @RegisterExtension private final HttpRequesterResource clientResource; public ClassicServerBootstrapFilterTest(final URIScheme scheme) { this.scheme = scheme; this.serverResource = new HttpServerResource(); this.serverResource.configure(bootstrap -> bootstrap .setSslContext(scheme == URIScheme.HTTPS ? SSLTestContexts.createServerSSLContext() : null) .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setRequestRouter(RequestRouter.builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", new EchoHandler()) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()) .addFilterLast("test-filter", (request, responseTrigger, context, chain) -> chain.proceed(request, new HttpFilterChain.ResponseTrigger() { @Override public void sendInformation( final ClassicHttpResponse response) throws HttpException, IOException { responseTrigger.sendInformation(response); } @Override public void submitResponse( final ClassicHttpResponse response) throws HttpException, IOException { response.setHeader("X-Test-Filter", "active"); responseTrigger.submitResponse(response); } }, context))); this.clientResource = new HttpRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setSslContext(SSLTestContexts.createClientSSLContext()) .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .build())); } @Test void testFilters() throws Exception { final HttpServer server = serverResource.start(); final HttpRequester requester = clientResource.start(); server.start(); final HttpHost target = new HttpHost(scheme.id, "localhost", server.getLocalPort()); final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/filters"); request.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN)); try (final ClassicHttpResponse response = requester.execute(target, request, TIMEOUT, context)) { assertThat(response.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final Header testFilterHeader = response.getHeader("X-Test-Filter"); assertThat(testFilterHeader, CoreMatchers.notNullValue()); } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicTLSIntegrationTest.java0100644 0000000 0000000 00000037210 00000250600 032235 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import static org.hamcrest.MatcherAssert.assertThat; import java.io.IOException; import java.net.InetAddress; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.impl.bootstrap.HttpRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpServer; import org.apache.hc.core5.http.impl.bootstrap.RequesterBootstrap; import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.ssl.TLS; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.ssl.SSLContexts; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.RegisterExtension; class ClassicTLSIntegrationTest { private static final Timeout TIMEOUT = Timeout.ofSeconds(30); private HttpServer server; @RegisterExtension public final AfterEachCallback serverCleanup = new AfterEachCallback() { @Override public void afterEach(final ExtensionContext context) throws Exception { if (server != null) { try { server.close(CloseMode.IMMEDIATE); } catch (final Exception ignore) { } } } }; private HttpRequester requester; @RegisterExtension public final AfterEachCallback clientCleanup = new AfterEachCallback() { @Override public void afterEach(final ExtensionContext context) throws Exception { if (requester != null) { try { requester.close(CloseMode.GRACEFUL); } catch (final Exception ignore) { } } } }; @Test void testTLSSuccess() throws Exception { server = ServerBootstrap.bootstrap() .setLocalAddress(InetAddress.getLoopbackAddress()) .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setSslContext(SSLTestContexts.createServerSSLContext()) .setExceptionListener(LoggingExceptionListener.INSTANCE) .setStreamListener(LoggingHttp1StreamListener.INSTANCE) .setRequestRouter(RequestRouter.builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", new EchoHandler()) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()) .create(); server.start(); final AtomicReference sslSessionRef = new AtomicReference<>(); requester = RequesterBootstrap.bootstrap() .setSslContext(SSLTestContexts.createClientSSLContext()) .setSslSessionVerifier((endpoint, sslSession) -> sslSessionRef.set(sslSession)) .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setStreamListener(LoggingHttp1StreamListener.INSTANCE) .setConnPoolListener(LoggingConnPoolListener.INSTANCE) .create(); final HttpCoreContext context = HttpCoreContext.create() ; final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort()); final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff"); request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN)); try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) { assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body1 = EntityUtils.toString(response1.getEntity()); assertThat(body1, CoreMatchers.equalTo("some stuff")); } final SSLSession sslSession = sslSessionRef.getAndSet(null); final ProtocolVersion tlsVersion = TLS.parse(sslSession.getProtocol()); assertThat(tlsVersion.greaterEquals(TLS.V_1_2.getVersion()), CoreMatchers.equalTo(true)); assertThat(sslSession.getPeerPrincipal().getName(), CoreMatchers.equalTo("CN=localhost,OU=Apache HttpComponents,O=Apache Software Foundation")); } @Test void testTLSTrustFailure() throws Exception { server = ServerBootstrap.bootstrap() .setLocalAddress(InetAddress.getLoopbackAddress()) .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setSslContext(SSLTestContexts.createServerSSLContext()) .setExceptionListener(LoggingExceptionListener.INSTANCE) .setStreamListener(LoggingHttp1StreamListener.INSTANCE) .register("*", new EchoHandler()) .create(); server.start(); requester = RequesterBootstrap.bootstrap() .setSslContext(SSLContexts.createDefault()) .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setStreamListener(LoggingHttp1StreamListener.INSTANCE) .setConnPoolListener(LoggingConnPoolListener.INSTANCE) .create(); final HttpCoreContext context = HttpCoreContext.create() ; final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort()); final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff"); request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN)); Assertions.assertThrows(IOException.class, () -> { try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) { EntityUtils.consume(response1.getEntity()); } }); } @Test void testTLSClientAuthFailure() throws Exception { server = ServerBootstrap.bootstrap() .setLocalAddress(InetAddress.getLoopbackAddress()) .setSslContext(SSLTestContexts.createClientSSLContext()) .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setSslContext(SSLTestContexts.createServerSSLContext()) .setSslSetupHandler(sslParameters -> sslParameters.setNeedClientAuth(true)) .setExceptionListener(LoggingExceptionListener.INSTANCE) .setStreamListener(LoggingHttp1StreamListener.INSTANCE) .register("*", new EchoHandler()) .create(); server.start(); requester = RequesterBootstrap.bootstrap() .setSslContext(SSLTestContexts.createClientSSLContext()) .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setStreamListener(LoggingHttp1StreamListener.INSTANCE) .setConnPoolListener(LoggingConnPoolListener.INSTANCE) .create(); final HttpCoreContext context = HttpCoreContext.create() ; final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort()); final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff"); request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN)); Assertions.assertThrows(IOException.class, () -> { try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) { EntityUtils.consume(response1.getEntity()); } }); } @Test void testSSLDisabledByDefault() throws Exception { server = ServerBootstrap.bootstrap() .setLocalAddress(InetAddress.getLoopbackAddress()) .setSslContext(SSLTestContexts.createServerSSLContext()) .setSslSetupHandler(sslParameters -> sslParameters.setProtocols(new String[]{"SSLv3"})) .setRequestRouter((r, c) -> null) .create(); server.start(); requester = RequesterBootstrap.bootstrap() .setSslContext(SSLTestContexts.createClientSSLContext()) .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setStreamListener(LoggingHttp1StreamListener.INSTANCE) .setConnPoolListener(LoggingConnPoolListener.INSTANCE) .create(); final HttpCoreContext context = HttpCoreContext.create() ; final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort()); final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff"); request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN)); Assertions.assertThrows(IOException.class, () -> { try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) { EntityUtils.consume(response1.getEntity()); } }); } @Test void testWeakCiphersDisabledByDefault() throws Exception { requester = RequesterBootstrap.bootstrap() .setSslContext(SSLTestContexts.createClientSSLContext()) .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setStreamListener(LoggingHttp1StreamListener.INSTANCE) .setConnPoolListener(LoggingConnPoolListener.INSTANCE) .create(); final String[] weakCiphersSuites = { "SSL_RSA_WITH_RC4_128_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_DH_anon_WITH_AES_128_CBC_SHA", "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_RSA_WITH_NULL_SHA", "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", "TLS_DH_anon_WITH_AES_256_GCM_SHA384", "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_NULL_SHA256", "SSL_RSA_EXPORT_WITH_RC4_40_MD5", "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", "TLS_KRB5_EXPORT_WITH_RC4_40_SHA", "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5" }; for (final String cipherSuite : weakCiphersSuites) { server = ServerBootstrap.bootstrap() .setLocalAddress(InetAddress.getLoopbackAddress()) .setSslContext(SSLTestContexts.createServerSSLContext()) .setRequestRouter((r, c) -> null) .setSslSetupHandler(sslParameters -> sslParameters.setCipherSuites(new String[]{cipherSuite})) .create(); Assertions.assertThrows(Exception.class, () -> { try { server.start(); final HttpCoreContext context = HttpCoreContext.create(); final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort()); final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff"); request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN)); try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) { EntityUtils.consume(response1.getEntity()); } } finally { server.close(CloseMode.IMMEDIATE); } }); } } @Test void testHostNameVerification() throws Exception { server = ServerBootstrap.bootstrap() .setLocalAddress(InetAddress.getLoopbackAddress()) .setSslContext(SSLTestContexts.createServerSSLContext()) .setRequestRouter((r, c) -> null) .create(); server.start(); requester = RequesterBootstrap.bootstrap() .setSslContext(SSLTestContexts.createClientSSLContext()) .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setStreamListener(LoggingHttp1StreamListener.INSTANCE) .setConnPoolListener(LoggingConnPoolListener.INSTANCE) .create(); final HttpCoreContext context = HttpCoreContext.create(); final HttpHost target1 = new HttpHost("https", InetAddress.getLoopbackAddress(), "localhost", server.getLocalPort()); final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff"); request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN)); try (final ClassicHttpResponse response1 = requester.execute(target1, request1, TIMEOUT, context)) { EntityUtils.consume(response1.getEntity()); } Assertions.assertThrows(SSLHandshakeException.class, () -> { final HttpHost target2 = new HttpHost("https", InetAddress.getLoopbackAddress(), "some-other-host", server.getLocalPort()); final ClassicHttpRequest request2 = new BasicClassicHttpRequest(Method.POST, "/stuff"); request2.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN)); try (final ClassicHttpResponse response2 = requester.execute(target2, request2, TIMEOUT, context)) { EntityUtils.consume(response2.getEntity()); } }); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/EchoHandler.java0100644 0000000 0000000 00000004475 00000250600 027410 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import java.io.IOException; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.entity.ByteArrayEntity; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.protocol.HttpContext; public class EchoHandler implements HttpRequestHandler { @Override public void handle( final ClassicHttpRequest request, final ClassicHttpResponse response, final HttpContext context) throws HttpException, IOException { response.setCode(HttpStatus.SC_OK); final HttpEntity entity = request.getEntity(); final byte[] data; if (entity != null) { data = EntityUtils.toByteArray(entity); response.setEntity(new ByteArrayEntity(data, ContentType.parse(entity.getContentType()))); } } } ././@LongLink0100644 0000000 0000000 00000000175 00000250600 011614 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/MonitoringResponseOutOfOrderStrategyIntegrationTest.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/MonitoringResponseOutOfOrderStra0100644 0000000 0000000 00000015307 00000250600 032757 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.classic; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpServer; import org.apache.hc.core5.http.impl.io.DefaultBHttpClientConnectionFactory; import org.apache.hc.core5.http.impl.io.MonitoringResponseOutOfOrderStrategy; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.entity.AbstractHttpEntity; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.extension.classic.HttpRequesterResource; import org.apache.hc.core5.testing.extension.classic.HttpServerResource; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout.ThreadMode; import org.junit.jupiter.api.extension.RegisterExtension; abstract class MonitoringResponseOutOfOrderStrategyIntegrationTest { private static final Timeout TIMEOUT = Timeout.ofSeconds(3); private final URIScheme scheme; @RegisterExtension private final HttpServerResource serverResource; @RegisterExtension private final HttpRequesterResource clientResource; public MonitoringResponseOutOfOrderStrategyIntegrationTest(final URIScheme scheme) { this.scheme = scheme; this.serverResource = new HttpServerResource(); this.serverResource.configure(bootstrap -> bootstrap .setSslContext(scheme == URIScheme.HTTPS ? SSLTestContexts.createServerSSLContext() : null) .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .setSoKeepAlive(false) .setTcpNoDelay(true) .build()) .setRequestRouter(RequestRouter.builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", (request, response, context) -> { response.setCode(400); response.setEntity(new StringEntity("stop")); }) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build())); this.clientResource = new HttpRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setSslContext(SSLTestContexts.createClientSSLContext()) .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .setSoKeepAlive(false) .build()) .setConnectionFactory(DefaultBHttpClientConnectionFactory.builder() .responseOutOfOrderStrategy(MonitoringResponseOutOfOrderStrategy.INSTANCE) .build())); } @Test @org.junit.jupiter.api.Timeout(value = 1, unit = TimeUnit.MINUTES, threadMode = ThreadMode.SEPARATE_THREAD) void testResponseOutOfOrderWithDefaultStrategy() throws Exception { final HttpServer server = serverResource.start(); final HttpRequester requester = clientResource.start(); final HttpCoreContext context = HttpCoreContext.create(); final HttpHost host = new HttpHost(scheme.id, "localhost", server.getLocalPort()); final ClassicHttpRequest post = new BasicClassicHttpRequest(Method.POST, "/"); final AllOnesHttpEntity requestEntity = new AllOnesHttpEntity(20 * 1024 * 1024); post.setEntity(requestEntity); try (final ClassicHttpResponse response = requester.execute(host, post, TIMEOUT, context)) { Assertions.assertEquals(400, response.getCode()); EntityUtils.consumeQuietly(response.getEntity()); } Assertions.assertTrue(requestEntity.remaining > 0, "Client should have stopped sending data"); } private static final class AllOnesHttpEntity extends AbstractHttpEntity { long remaining; protected AllOnesHttpEntity(final long length) { super(ContentType.APPLICATION_OCTET_STREAM, null, true); this.remaining = length; } @Override public InputStream getContent() { throw new UnsupportedOperationException(); } @Override public void writeTo(final OutputStream outStream) throws IOException { final byte[] buf = new byte[1024]; while (remaining > 0) { final int writeLength = (int) Math.min(remaining, buf.length); outStream.write(buf, 0, writeLength); outStream.flush(); remaining -= writeLength; } } @Override public boolean isStreaming() { return true; } @Override public void close() { } @Override public long getContentLength() { return -1L; } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/0040755 0000000 0000000 00000000000 00000250600 025612 5ustar000000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/ApacheHttpDCompatIT.java0100644 0000000 0000000 00000010156 00000250600 032203 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.compatibility; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.testing.compatibility.classic.ClassicHttpCompatTest; import org.apache.hc.core5.testing.compatibility.nio.AsyncHttp1CompatTest; import org.apache.hc.core5.testing.compatibility.nio.AsyncHttp2CompatTest; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @Testcontainers(disabledWithoutDocker = true) class ApacheHttpDCompatIT { private static Network NETWORK = Network.newNetwork(); @Container static final GenericContainer HTTPD_CONTAINER = ContainerImages.apacheHttpD(NETWORK); @AfterAll static void cleanup() { HTTPD_CONTAINER.close(); } static HttpHost targetContainerHost() { return new HttpHost(URIScheme.HTTP.id, HTTPD_CONTAINER.getHost(), HTTPD_CONTAINER.getMappedPort(ContainerImages.HTTP_PORT)); } static HttpHost targetContainerH2CHost() { return new HttpHost(URIScheme.HTTP.id, HTTPD_CONTAINER.getHost(), HTTPD_CONTAINER.getMappedPort(ContainerImages.H2C_PORT)); } static HttpHost targetContainerTLSHost() { return new HttpHost(URIScheme.HTTPS.id, HTTPD_CONTAINER.getHost(), HTTPD_CONTAINER.getMappedPort(ContainerImages.HTTPS_PORT)); } @Nested @DisplayName("Classic, HTTP/1, plain") class ClassicHttp1 extends ClassicHttpCompatTest { public ClassicHttp1() throws Exception { super(targetContainerHost()); } } @Nested @DisplayName("Classic, HTTP/1, TLS") class ClassicHttp1Tls extends ClassicHttpCompatTest { public ClassicHttp1Tls() throws Exception { super(targetContainerTLSHost()); } } @Nested @DisplayName("Async, HTTP/1, plain") class AsyncHttp1 extends AsyncHttp1CompatTest { public AsyncHttp1() throws Exception { super(targetContainerHost()); } } @Nested @DisplayName("Async, HTTP/2, plain") class AsyncHttp2 extends AsyncHttp2CompatTest { public AsyncHttp2() throws Exception { super(targetContainerH2CHost(), HttpVersionPolicy.FORCE_HTTP_2); } } @Nested @DisplayName("Async, HTTP/1, TLS") class AsyncHttp1Tls extends AsyncHttp1CompatTest { public AsyncHttp1Tls() throws Exception { super(targetContainerTLSHost()); } } @Nested @DisplayName("Async, protocol HTTP/2, TLS") class AsyncHttp2Tls extends AsyncHttp2CompatTest { public AsyncHttp2Tls() throws Exception { super(targetContainerTLSHost(), HttpVersionPolicy.FORCE_HTTP_2); } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/ContainerImages.java0100644 0000000 0000000 00000024145 00000250600 031530 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.compatibility; import java.util.Random; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; import org.testcontainers.containers.output.Slf4jLogConsumer; import org.testcontainers.images.builder.ImageFromDockerfile; import org.testcontainers.images.builder.Transferable; import org.testcontainers.utility.DockerImageName; public final class ContainerImages { private static final Logger LOG = LoggerFactory.getLogger(ContainerImages.class); public static final String AAA = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; public static final String BBB = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; public static final String CCC = "ccccccccccccccccccccccccccccccccccccccccccccccccc"; public static final String PUSHY = "I am being very pushy"; public static final String APACHE_HTTPD = "test-apache"; public static final String NGINX = "test-nginx"; public static final String HTTPBIN = "test-httpbin"; public static final String DANTE = "test-dante"; public static final String JETTY = "test-jetty"; public static int HTTP_PORT = 80; public static int H2C_PORT = 81; public static int HTTPS_PORT = 443; public static int SOCKS_PORT = 1080; public static int HTTP_EXT_PORT = 8080; public static int HTTPS_EXT_PORT = 8443; static byte[] blob() { final Random random = new Random(System.currentTimeMillis()); final byte[] bytes = new byte[5 * 1024 * 1024 + random.nextInt(102400)]; for (int i = 0; i < bytes.length; i++) { bytes[i] = (byte) ('0' + random.nextInt(10)); } return bytes; } public static GenericContainer httpBin(final Network network) { return new GenericContainer<>(DockerImageName.parse("kennethreitz/httpbin:latest")) .withNetwork(network) .withNetworkAliases(HTTPBIN) .withLogConsumer(new Slf4jLogConsumer(LOG)) .withExposedPorts(HTTP_PORT); } public static GenericContainer apacheHttpD(final Network network) { return new GenericContainer<>(new ImageFromDockerfile() .withFileFromClasspath("httpd-default.conf", "docker/httpd/httpd-default.conf") .withFileFromClasspath("httpd-h2c.conf", "docker/httpd/httpd-h2c.conf") .withFileFromClasspath("httpd-ssl.conf", "docker/httpd/httpd-ssl.conf") .withFileFromClasspath("server-cert.pem", "docker/server-cert.pem") .withFileFromClasspath("server-key.pem", "docker/server-key.pem") .withFileFromString("pushy", PUSHY) .withFileFromString("aaa", AAA) .withFileFromString("bbb", BBB) .withFileFromString("ccc", CCC) .withFileFromTransferable("blob", Transferable.of(blob())) .withDockerfileFromBuilder(builder -> builder .from("httpd:2.4") .env("var_dir", "/var/httpd") .env("www_dir", "${var_dir}/www") .run("mkdir -p ${var_dir}") .run("mkdir -p ${www_dir}") .run("echo '\\n" + "LoadModule http2_module modules/mod_http2.so\\n" + "LoadModule ssl_module modules/mod_ssl.so\\n" + "Include conf/extra/httpd-default.conf\\n" + "Include conf/extra/httpd-h2c.conf\\n" + "Include conf/extra/httpd-ssl.conf\\n" + "'" + " >> /usr/local/apache2/conf/httpd.conf") .copy("httpd-default.conf", "/usr/local/apache2/conf/extra/httpd-default.conf") .copy("httpd-h2c.conf", "/usr/local/apache2/conf/extra/httpd-h2c.conf") .copy("httpd-ssl.conf", "/usr/local/apache2/conf/extra/httpd-ssl.conf") .copy("server-cert.pem", "/usr/local/apache2/conf/server-cert.pem") .copy("server-key.pem", "/usr/local/apache2/conf/server-key.pem") .copy("pushy", "${www_dir}/") .copy("aaa", "${www_dir}/") .copy("bbb", "${www_dir}/") .copy("ccc", "${www_dir}/") .copy("blob", "${www_dir}/") .build())) .withNetwork(network) .withNetworkAliases(APACHE_HTTPD) .withLogConsumer(new Slf4jLogConsumer(LOG)) .withExposedPorts(HTTP_PORT, H2C_PORT, HTTPS_PORT); } public static GenericContainer nginx(final Network network) { return new GenericContainer<>(new ImageFromDockerfile() .withFileFromClasspath("default.conf", "docker/nginx/default.conf") .withFileFromClasspath("h2c.conf", "docker/nginx/h2c.conf") .withFileFromClasspath("ssl.conf", "docker/nginx/ssl.conf") .withFileFromClasspath("server-cert.pem", "docker/server-cert.pem") .withFileFromClasspath("server-key.pem", "docker/server-key.pem") .withFileFromString("pushy", PUSHY) .withFileFromString("aaa", AAA) .withFileFromString("bbb", BBB) .withFileFromString("ccc", CCC) .withFileFromTransferable("blob", Transferable.of(blob())) .withDockerfileFromBuilder(builder -> builder .from("nginx:1.23") .env("var_dir", "/var/nginx") .env("www_dir", "${var_dir}/www") .run("mkdir -p ${var_dir}") .copy("default.conf", "/etc/nginx/conf.d/default.conf") .copy("h2c.conf", "/etc/nginx/conf.d/h2c.conf") .copy("ssl.conf", "/etc/nginx/conf.d/ssl.conf") .copy("server-cert.pem", "/etc/nginx/server-cert.pem") .copy("server-key.pem", "/etc/nginx/server-key.pem") .copy("pushy", "${www_dir}/") .copy("aaa", "${www_dir}/") .copy("bbb", "${www_dir}/") .copy("ccc", "${www_dir}/") .copy("blob", "${www_dir}/") .build())) .withNetwork(network) .withNetworkAliases(NGINX) .withLogConsumer(new Slf4jLogConsumer(LOG)) .withExposedPorts(HTTP_PORT, H2C_PORT, HTTPS_PORT); } public static GenericContainer dante(final Network network) { return new GenericContainer<>(new ImageFromDockerfile() .withDockerfileFromBuilder(builder -> builder .from("vimagick/dante:latest") .run("useradd socks") .run("echo socks:nopassword | chpasswd") .build())) .withNetwork(network) .withNetworkAliases(DANTE) .withLogConsumer(new Slf4jLogConsumer(LOG)) .withExposedPorts(SOCKS_PORT); } public static GenericContainer jetty(final Network network) { return new GenericContainer<>(new ImageFromDockerfile() .withFileFromClasspath("server.p12", "docker/server.p12") .withFileFromString("aaa", AAA) .withFileFromString("bbb", BBB) .withFileFromString("ccc", CCC) .withFileFromTransferable("blob", Transferable.of(blob())) .withDockerfileFromBuilder(builder -> builder .from("jetty:12.0-jdk17-amazoncorretto") .env("jetty_base", "/var/lib/jetty/") .env("webapp_root", "${jetty_base}/webapps/ROOT") .env("uid", "jetty") .user("root") .copy("server.p12", "${jetty_base}/etc/keystore.p12") .run("echo 'jetty.sslContext.keyStorePassword=nopassword' >> ${jetty_base}/start.d/ssl.ini") .run("mkdir -p ${webapp_root}/") .copy("aaa", "${webapp_root}/") .copy("bbb", "${webapp_root}/") .copy("ccc", "${webapp_root}/") .copy("blob", "${webapp_root}/") .run("chown -R ${uid}:${uid} ${jetty_base}/") .user("${uid}:${uid}") .run("java -jar ${JETTY_HOME}/start.jar --add-modules=http2 --add-modules=ee10-deploy --approve-all-licenses") .build())) .withNetwork(network) .withNetworkAliases(JETTY) .withLogConsumer(new Slf4jLogConsumer(LOG)) .withExposedPorts(HTTP_EXT_PORT, HTTPS_EXT_PORT); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/DanteHttpBinCompatIT.java0100644 0000000 0000000 00000006326 00000250600 032406 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.compatibility; import java.net.InetSocketAddress; import java.net.SocketAddress; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.testing.compatibility.classic.SocksHttpBinClassicCompatTest; import org.apache.hc.core5.testing.compatibility.nio.SocksHttpBinAsyncCompatTest; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @Testcontainers(disabledWithoutDocker = true) class DanteHttpBinCompatIT { private static Network NETWORK = Network.newNetwork(); @Container static final GenericContainer DANTE_CONTAINER = ContainerImages.dante(NETWORK); @Container static final GenericContainer HTTP_BIN_CONTAINER = ContainerImages.httpBin(NETWORK); @AfterAll static void cleanup() { DANTE_CONTAINER.close(); HTTP_BIN_CONTAINER.close(); } static SocketAddress socketContainerAddress() { return new InetSocketAddress(DANTE_CONTAINER.getHost(), DANTE_CONTAINER.getMappedPort(ContainerImages.SOCKS_PORT)); } static HttpHost targetInternalHost() { return new HttpHost(URIScheme.HTTP.id, ContainerImages.HTTPBIN, ContainerImages.HTTP_PORT); } static String SOCKS_USER = "socks"; static String SOCKS_PW = "nopassword"; @Nested @DisplayName("Classic, SOCKS proxy") class Classic extends SocksHttpBinClassicCompatTest { public Classic() throws Exception { super(targetInternalHost(), socketContainerAddress(), SOCKS_USER, SOCKS_PW); } } @Nested @DisplayName("Async, SOCKS proxy") class Async extends SocksHttpBinAsyncCompatTest { public Async() throws Exception { super(targetInternalHost(), socketContainerAddress(), SOCKS_USER, SOCKS_PW); } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/HttpBinCompatIT.java0100644 0000000 0000000 00000005230 00000250600 031423 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.compatibility; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.testing.compatibility.classic.HttpBinClassicCompatTest; import org.apache.hc.core5.testing.compatibility.nio.HttpBinAsyncCompatTest; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @Testcontainers(disabledWithoutDocker = true) class HttpBinCompatIT { private static Network NETWORK = Network.newNetwork(); @Container static final GenericContainer HTTP_BIN_CONTAINER = ContainerImages.httpBin(NETWORK); @AfterAll static void cleanup() { HTTP_BIN_CONTAINER.close(); } static HttpHost targetContainerHost() { return new HttpHost(URIScheme.HTTP.id, HTTP_BIN_CONTAINER.getHost(), HTTP_BIN_CONTAINER.getMappedPort(ContainerImages.HTTP_PORT)); } @Nested @DisplayName("Classic") class Classic extends HttpBinClassicCompatTest { public Classic() throws Exception { super(targetContainerHost()); } } @Nested @DisplayName("Async") class Async extends HttpBinAsyncCompatTest { public Async() throws Exception { super(targetContainerHost()); } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/JettyCompatIT.java0100644 0000000 0000000 00000006374 00000250600 031164 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.compatibility; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.testing.compatibility.classic.ClassicHttpCompatTest; import org.apache.hc.core5.testing.compatibility.nio.AsyncHttp1CompatTest; import org.apache.hc.core5.testing.compatibility.nio.AsyncHttp2CompatNoPushTest; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @Testcontainers(disabledWithoutDocker = true) class JettyCompatIT { private static Network NETWORK = Network.newNetwork(); @Container static final GenericContainer JETTY_CONTAINER = ContainerImages.jetty(NETWORK); @AfterAll static void cleanup() { JETTY_CONTAINER.close(); } static HttpHost targetContainerHost() { return new HttpHost(URIScheme.HTTP.id, JETTY_CONTAINER.getHost(), JETTY_CONTAINER.getMappedPort(ContainerImages.HTTP_EXT_PORT)); } static HttpHost targetContainerTLSHost() { return new HttpHost(URIScheme.HTTPS.id, JETTY_CONTAINER.getHost(), JETTY_CONTAINER.getMappedPort(ContainerImages.HTTPS_EXT_PORT)); } @Nested @DisplayName("Classic, HTTP/1, plain") class ClassicHttp1 extends ClassicHttpCompatTest { public ClassicHttp1() throws Exception { super(targetContainerHost()); } } @Nested @DisplayName("Async, HTTP/1, plain") class AsyncHttp1 extends AsyncHttp1CompatTest { public AsyncHttp1() throws Exception { super(targetContainerHost()); } } @Nested @DisplayName("Async, HTTP/2, TLS") class AsyncHttp2Tls extends AsyncHttp2CompatNoPushTest { public AsyncHttp2Tls() throws Exception { super(targetContainerTLSHost(), HttpVersionPolicy.FORCE_HTTP_2); } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/NginxCompatIT.java0100644 0000000 0000000 00000010131 00000250600 031132 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.compatibility; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.testing.compatibility.classic.ClassicHttpCompatTest; import org.apache.hc.core5.testing.compatibility.nio.AsyncHttp1CompatTest; import org.apache.hc.core5.testing.compatibility.nio.AsyncHttp2CompatTest; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @Testcontainers(disabledWithoutDocker = true) class NginxCompatIT { private static Network NETWORK = Network.newNetwork(); @Container static final GenericContainer NGINX_CONTAINER = ContainerImages.nginx(NETWORK); @AfterAll static void cleanup() { NGINX_CONTAINER.close(); } static HttpHost targetContainerHost() { return new HttpHost(URIScheme.HTTP.id, NGINX_CONTAINER.getHost(), NGINX_CONTAINER.getMappedPort(ContainerImages.HTTP_PORT)); } static HttpHost targetContainerH2CHost() { return new HttpHost(URIScheme.HTTP.id, NGINX_CONTAINER.getHost(), NGINX_CONTAINER.getMappedPort(ContainerImages.H2C_PORT)); } static HttpHost targetContainerTLSHost() { return new HttpHost(URIScheme.HTTPS.id, NGINX_CONTAINER.getHost(), NGINX_CONTAINER.getMappedPort(ContainerImages.HTTPS_PORT)); } @Nested @DisplayName("Classic, HTTP/1, plain") class ClassicHttp1 extends ClassicHttpCompatTest { public ClassicHttp1() throws Exception { super(targetContainerHost()); } } @Nested @DisplayName("Classic, HTTP/1, TLS") class ClassicHttp1Tls extends ClassicHttpCompatTest { public ClassicHttp1Tls() throws Exception { super(targetContainerTLSHost()); } } @Nested @DisplayName("Async, HTTP/1, plain") class AsyncHttp1 extends AsyncHttp1CompatTest { public AsyncHttp1() throws Exception { super(targetContainerHost()); } } @Nested @DisplayName("Async, HTTP/2, plain") class AsyncHttp2 extends AsyncHttp2CompatTest { public AsyncHttp2() throws Exception { super(targetContainerH2CHost(), HttpVersionPolicy.FORCE_HTTP_2); } } @Nested @DisplayName("Async, HTTP/1, TLS") class AsyncHttp1Tls extends AsyncHttp1CompatTest { public AsyncHttp1Tls() throws Exception { super(targetContainerTLSHost()); } } @Nested @DisplayName("Async, HTTP/2, TLS") class AsyncHttp2Tls extends AsyncHttp2CompatTest { public AsyncHttp2Tls() throws Exception { super(targetContainerTLSHost(), HttpVersionPolicy.FORCE_HTTP_2); } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/TLSTestContexts.java0100644 0000000 0000000 00000004202 00000250600 031502 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.compatibility; import java.io.IOException; import java.net.URL; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import javax.net.ssl.SSLContext; import org.apache.hc.core5.ssl.SSLContextBuilder; public final class TLSTestContexts { public static SSLContext createClientSSLContext() { final URL keyStoreURL = TLSTestContexts.class.getResource("/test-ca.jks"); final String storePassword = "nopassword"; try { return SSLContextBuilder.create() .loadTrustMaterial(keyStoreURL, storePassword.toCharArray()) .build(); } catch (final NoSuchAlgorithmException | KeyManagementException | KeyStoreException | CertificateException | IOException ex) { throw new IllegalStateException(ex); } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/classic/0040755 0000000 0000000 00000000000 00000250600 027233 5ustar000000000 0000000 ././@LongLink0100644 0000000 0000000 00000000155 00000250600 011612 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/classic/ClassicHttpCompatTest.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/classic/ClassicHttpCompatT0100644 0000000 0000000 00000014626 00000250600 032675 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.compatibility.classic; import static org.hamcrest.MatcherAssert.assertThat; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.impl.bootstrap.HttpRequester; import org.apache.hc.core5.http.impl.bootstrap.RequesterBootstrap; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.testing.Result; import org.apache.hc.core5.testing.compatibility.ContainerImages; import org.apache.hc.core5.testing.compatibility.TLSTestContexts; import org.apache.hc.core5.testing.extension.classic.HttpRequesterResource; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; public abstract class ClassicHttpCompatTest { static final Timeout TIMEOUT = Timeout.ofSeconds(5); private final HttpHost target; @RegisterExtension private final HttpRequesterResource clientResource; public ClassicHttpCompatTest(final HttpHost target) { this.target = target; this.clientResource = new HttpRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setSslContext(TLSTestContexts.createClientSSLContext()) ); } void configure(final Consumer customizer) { clientResource.configure(customizer); } HttpRequester client() { return clientResource.start(); } @Test void test_sequential_requests() throws Exception { final HttpRequester requester = client(); final int n = 20; for (int i = 0; i < n; i++) { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = ClassicRequestBuilder.get("/aaa") .setHttpHost(target) .build(); requester.execute(target, request, TIMEOUT, context, response -> { assertThat(response.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body1 = EntityUtils.toString(response.getEntity()); assertThat(body1, CoreMatchers.equalTo(ContainerImages.AAA)); return null; }); } } @Test void test_multi_threaded_requests() throws Exception { final HttpRequester requester = client(); final int c = 10; final AtomicInteger n = new AtomicInteger(20 * c); final CountDownLatch countDownLatch = new CountDownLatch(c); final Queue> resultQueue = new ConcurrentLinkedQueue<>(); final ExecutorService executorService = Executors.newFixedThreadPool(c); try { for (int i = 0; i < c; i++) { executorService.execute(() -> { try { while (n.decrementAndGet() > 0) { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = ClassicRequestBuilder.get("/aaa") .setHttpHost(target) .build(); try { requester.execute(target, request, TIMEOUT, context, response -> { resultQueue.add(new Result<>( request, response, EntityUtils.toString(response.getEntity()))); return null; }); } catch (final Exception ex) { resultQueue.add(new Result<>(request, ex)); } } } finally { countDownLatch.countDown(); } }); } Assertions.assertTrue(countDownLatch.await(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()), "Request executions have not completed in time"); for (final Result result : resultQueue) { if (result.isOK()) { Assertions.assertNotNull(result.response); Assertions.assertEquals(HttpStatus.SC_OK, result.response.getCode(), "Response message returned non 200 status"); } else { Assertions.fail(result.exception); } } } finally { executorService.shutdownNow(); } } } ././@LongLink0100644 0000000 0000000 00000000160 00000250600 011606 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/classic/HttpBinClassicCompatTest.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/classic/HttpBinClassicComp0100644 0000000 0000000 00000011614 00000250600 032647 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.compatibility.classic; import static org.hamcrest.MatcherAssert.assertThat; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.impl.bootstrap.HttpRequester; import org.apache.hc.core5.http.impl.bootstrap.RequesterBootstrap; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.testing.extension.classic.HttpRequesterResource; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; public abstract class HttpBinClassicCompatTest { static final Timeout TIMEOUT = Timeout.ofSeconds(5); private final HttpHost target; @RegisterExtension private final HttpRequesterResource clientResource; public HttpBinClassicCompatTest(final HttpHost target) { this.target = target; this.clientResource = new HttpRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .build()) ); } void configure(final Consumer customizer) { clientResource.configure(customizer); } HttpRequester client() { return clientResource.start(); } @Test void test_sequential_request_execution() throws Exception { final HttpRequester client = client(); final List requestMessages = Arrays.asList( ClassicRequestBuilder.get("/headers") .setHttpHost(target) .build(), ClassicRequestBuilder.post("/anything") .setHttpHost(target) .setEntity(new StringEntity("some important message", ContentType.TEXT_PLAIN)) .build(), ClassicRequestBuilder.put("/anything") .setHttpHost(target) .setEntity(new StringEntity("some important message", ContentType.TEXT_PLAIN)) .build(), ClassicRequestBuilder.get("/drip") .setHttpHost(target) .build(), ClassicRequestBuilder.get("/bytes/20000") .setHttpHost(target) .build(), ClassicRequestBuilder.get("/delay/2") .setHttpHost(target) .build(), ClassicRequestBuilder.post("/delay/2") .setHttpHost(target) .setEntity(new StringEntity("some important message", ContentType.TEXT_PLAIN)) .build(), ClassicRequestBuilder.put("/delay/2") .setHttpHost(target) .setEntity(new StringEntity("some important message", ContentType.TEXT_PLAIN)) .build() ); for (final ClassicHttpRequest request : requestMessages) { final HttpCoreContext context = HttpCoreContext.create(); client.execute(target, request, TIMEOUT, context, response -> { assertThat(response.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); return null; }); } } } ././@LongLink0100644 0000000 0000000 00000000165 00000250600 011613 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/classic/SocksHttpBinClassicCompatTest.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/classic/SocksHttpBinClassi0100644 0000000 0000000 00000004262 00000250600 032671 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.compatibility.classic; import java.net.Authenticator; import java.net.PasswordAuthentication; import java.net.SocketAddress; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.io.SocketConfig; public abstract class SocksHttpBinClassicCompatTest extends HttpBinClassicCompatTest { public SocksHttpBinClassicCompatTest(final HttpHost target, final SocketAddress socksProxy, final String socksUser, final String socksPassword) { super(target); configure(bootstrap -> bootstrap .setSocketConfig(SocketConfig.custom() .setSocksProxyAddress(socksProxy) .setSoTimeout(TIMEOUT) .build()) ); Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(socksUser, socksPassword.toCharArray()); } }); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/0040755 0000000 0000000 00000000000 00000250600 026377 5ustar000000000 0000000 ././@LongLink0100644 0000000 0000000 00000000150 00000250600 011605 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/AsyncHttp1CompatTest.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/AsyncHttp1CompatTest.j0100644 0000000 0000000 00000005435 00000250600 032560 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.compatibility.nio; import java.util.function.Consumer; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.nio.ssl.BasicClientTlsStrategy; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2AsyncRequester; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.testing.compatibility.TLSTestContexts; import org.apache.hc.core5.testing.extension.nio.H2AsyncRequesterResource; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.extension.RegisterExtension; public abstract class AsyncHttp1CompatTest extends AsyncHttpCompatTest { static final Timeout TIMEOUT = Timeout.ofSeconds(5); @RegisterExtension private final H2AsyncRequesterResource clientResource; public AsyncHttp1CompatTest(final HttpHost target) { super(target); this.clientResource = new H2AsyncRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setTlsStrategy(new BasicClientTlsStrategy(TLSTestContexts.createClientSSLContext())) .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1)); } void configure(final Consumer customizer) { clientResource.configure(customizer); } @Override H2AsyncRequester client() { return clientResource.start(); } } ././@LongLink0100644 0000000 0000000 00000000156 00000250600 011613 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/AsyncHttp2CompatNoPushTest.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/AsyncHttp2CompatNoPush0100644 0000000 0000000 00000006050 00000250600 032620 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.compatibility.nio; import java.util.function.Consumer; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2AsyncRequester; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap; import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.testing.compatibility.TLSTestContexts; import org.apache.hc.core5.testing.extension.nio.H2AsyncRequesterResource; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.extension.RegisterExtension; public abstract class AsyncHttp2CompatNoPushTest extends AsyncHttpCompatTest { static final Timeout TIMEOUT = Timeout.ofSeconds(5); private final HttpHost target; @RegisterExtension private final H2AsyncRequesterResource clientResource; public AsyncHttp2CompatNoPushTest(final HttpHost target, final HttpVersionPolicy versionPolicy) { super(target); this.target = target; this.clientResource = new H2AsyncRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setH2Config(H2Config.custom() .setPushEnabled(true) .build()) .setTlsStrategy(new H2ClientTlsStrategy(TLSTestContexts.createClientSSLContext())) .setVersionPolicy(versionPolicy)); } void configure(final Consumer customizer) { clientResource.configure(customizer); } @Override H2AsyncRequester client() { return clientResource.start(); } } ././@LongLink0100644 0000000 0000000 00000000150 00000250600 011605 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/AsyncHttp2CompatTest.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/AsyncHttp2CompatTest.j0100644 0000000 0000000 00000016710 00000250600 032557 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.compatibility.nio; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.function.Consumer; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.RequestNotExecutedException; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.support.AbstractAsyncPushHandler; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2AsyncRequester; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap; import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.testing.Result; import org.apache.hc.core5.testing.compatibility.TLSTestContexts; import org.apache.hc.core5.testing.extension.nio.H2AsyncRequesterResource; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; public abstract class AsyncHttp2CompatTest extends AsyncHttpCompatTest { static final Timeout TIMEOUT = Timeout.ofSeconds(5); private final HttpHost target; @RegisterExtension private final H2AsyncRequesterResource clientResource; public AsyncHttp2CompatTest(final HttpHost target, final HttpVersionPolicy versionPolicy) { super(target); this.target = target; this.clientResource = new H2AsyncRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setH2Config(H2Config.custom() .setPushEnabled(true) .build()) .setTlsStrategy(new H2ClientTlsStrategy(TLSTestContexts.createClientSSLContext())) .setVersionPolicy(versionPolicy)); } void configure(final Consumer customizer) { clientResource.configure(customizer); } @Override H2AsyncRequester client() { return clientResource.start(); } @Test void test_request_execution_with_push() throws Exception { final H2AsyncRequester client = client(); final Future connectFuture = client.connect(target, TIMEOUT, null, null); final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); try { final CountDownLatch countDownLatch = new CountDownLatch(4); final Queue> resultQueue = new ConcurrentLinkedQueue<>(); final HttpRequest httpget = new BasicHttpRequest(Method.GET, target, "/pushy"); endpoint.execute( new BasicRequestProducer(httpget, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), (request, context) -> new AbstractAsyncPushHandler>(new BasicResponseConsumer<>(new StringAsyncEntityConsumer())) { @Override protected void handleResponse(final HttpRequest promise, final Message responseMessage) { resultQueue.add(new Result<>( httpget, responseMessage.getHead(), responseMessage.getBody())); countDownLatch.countDown(); } @Override protected void handleError(final HttpRequest promise, final Exception cause) { resultQueue.add(new Result<>(httpget, cause)); countDownLatch.countDown(); } }, null, new FutureCallback>() { @Override public void completed(final Message responseMessage) { resultQueue.add(new Result<>( httpget, responseMessage.getHead(), responseMessage.getBody())); countDownLatch.countDown(); } @Override public void failed(final Exception ex) { resultQueue.add(new Result<>(httpget, ex)); countDownLatch.countDown(); } @Override public void cancelled() { failed(new RequestNotExecutedException()); } }); Assertions.assertTrue(countDownLatch.await(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()), "Request executions have not completed in time"); for (final Result result : resultQueue) { if (result.isOK()) { Assertions.assertNotNull(result.response); Assertions.assertEquals(HttpStatus.SC_OK, result.response.getCode(), "Response message returned non 200 status"); } else { Assertions.fail(result.exception); } } } finally { endpoint.releaseAndDiscard(); } } } ././@LongLink0100644 0000000 0000000 00000000147 00000250600 011613 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/AsyncHttpCompatTest.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/AsyncHttpCompatTest.ja0100644 0000000 0000000 00000024062 00000250600 032635 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.compatibility.nio; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.RequestNotExecutedException; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.testing.Result; import org.apache.hc.core5.testing.compatibility.ContainerImages; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; public abstract class AsyncHttpCompatTest { static final Timeout TIMEOUT = Timeout.ofSeconds(5); private final HttpHost target; public AsyncHttpCompatTest(final HttpHost target) { this.target = target; } abstract T client(); @Test void test_multiple_request_execution_over_same_connection() throws Exception { final HttpAsyncRequester client = client(); final Future connectFuture = client.connect(target, TIMEOUT, null, null); final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); try { final int n = 20; final CountDownLatch countDownLatch = new CountDownLatch(n); final Queue> resultQueue = new ConcurrentLinkedQueue<>(); for (int i = 0; i < n; i++) { final HttpRequest httpget = new BasicHttpRequest(Method.GET, target, "/aaa"); endpoint.execute( new BasicRequestProducer(httpget, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), new FutureCallback>() { @Override public void completed(final Message responseMessage) { resultQueue.add(new Result<>( httpget, responseMessage.getHead(), responseMessage.getBody())); countDownLatch.countDown(); } @Override public void failed(final Exception ex) { resultQueue.add(new Result<>(httpget, ex)); countDownLatch.countDown(); } @Override public void cancelled() { failed(new RequestNotExecutedException()); } }); } Assertions.assertTrue(countDownLatch.await(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()), "Request executions have not completed in time"); for (final Result result : resultQueue) { if (result.isOK()) { Assertions.assertNotNull(result.response); Assertions.assertEquals(HttpStatus.SC_OK, result.response.getCode(), "Response message returned non 200 status"); Assertions.assertEquals(ContainerImages.AAA, result.content); } else { Assertions.fail(result.exception); } } } finally { endpoint.releaseAndDiscard(); } } @Test void test_multiple_request_execution_over_multiple_connections() throws Exception { final HttpAsyncRequester client = client(); final int c = 10; client.setDefaultMaxPerRoute(10); final int n = 20 * c; final CountDownLatch countDownLatch = new CountDownLatch(n); final Queue> resultQueue = new ConcurrentLinkedQueue<>(); for (int i = 0; i < n; i++) { final HttpRequest httpget = new BasicHttpRequest(Method.GET, target, "/aaa"); client.execute( target, new BasicRequestProducer(httpget, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, new FutureCallback>() { @Override public void completed(final Message responseMessage) { resultQueue.add(new Result<>( httpget, responseMessage.getHead(), responseMessage.getBody())); countDownLatch.countDown(); } @Override public void failed(final Exception ex) { resultQueue.add(new Result<>(httpget, ex)); countDownLatch.countDown(); } @Override public void cancelled() { failed(new RequestNotExecutedException()); } }); } Assertions.assertTrue(countDownLatch.await(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()), "Request executions have not completed in time"); for (final Result result : resultQueue) { if (result.isOK()) { Assertions.assertNotNull(result.response); Assertions.assertEquals(HttpStatus.SC_OK, result.response.getCode(), "Response message returned non 200 status"); Assertions.assertEquals(ContainerImages.AAA, result.content); } else { Assertions.fail(result.exception); } } } @Test void test_multiple_request_execution_large_blob() throws Exception { final HttpAsyncRequester client = client(); final Future connectFuture = client.connect(target, TIMEOUT, null, null); final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); try { final int n = 20; final CountDownLatch countDownLatch = new CountDownLatch(n); final Queue> resultQueue = new ConcurrentLinkedQueue<>(); for (int i = 0; i < n; i++) { final HttpRequest httpget = new BasicHttpRequest(Method.GET, target, "/blob"); endpoint.execute( new BasicRequestProducer(httpget, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), new FutureCallback>() { @Override public void completed(final Message responseMessage) { resultQueue.add(new Result<>( httpget, responseMessage.getHead(), responseMessage.getBody())); countDownLatch.countDown(); } @Override public void failed(final Exception ex) { resultQueue.add(new Result<>(httpget, ex)); countDownLatch.countDown(); } @Override public void cancelled() { failed(new RequestNotExecutedException()); } }); } Assertions.assertTrue(countDownLatch.await(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()), "Request executions have not completed in time"); for (final Result result : resultQueue) { if (result.isOK()) { Assertions.assertNotNull(result.response); Assertions.assertEquals(HttpStatus.SC_OK, result.response.getCode(), "Response message returned non 200 status"); } else { Assertions.fail(result.exception); } } } finally { endpoint.releaseAndDiscard(); } } } ././@LongLink0100644 0000000 0000000 00000000152 00000250600 011607 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/HttpBinAsyncCompatTest.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/HttpBinAsyncCompatTest0100644 0000000 0000000 00000022733 00000250600 032700 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.compatibility.nio; import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.function.Consumer; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.RequestNotExecutedException; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2AsyncRequester; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.testing.Result; import org.apache.hc.core5.testing.extension.nio.H2AsyncRequesterResource; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; public abstract class HttpBinAsyncCompatTest { static final Timeout TIMEOUT = Timeout.ofSeconds(5); private final HttpHost target; @RegisterExtension private final H2AsyncRequesterResource clientResource; public HttpBinAsyncCompatTest(final HttpHost target) { this.target = target; this.clientResource = new H2AsyncRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1)); } void configure(final Consumer customizer) { clientResource.configure(customizer); } H2AsyncRequester client() { return clientResource.start(); } @Test void test_sequential_request_execution() throws Exception { final HttpAsyncRequester client = client(); final List> requestMessages = Arrays.asList( new Message<>(new BasicHttpRequest(Method.GET, target, "/headers")), new Message<>( new BasicHttpRequest(Method.POST, target, "/anything"), new StringAsyncEntityProducer("some important message", ContentType.TEXT_PLAIN)), new Message<>( new BasicHttpRequest(Method.PUT, target, "/anything"), new StringAsyncEntityProducer("some important message", ContentType.TEXT_PLAIN)), new Message<>(new BasicHttpRequest(Method.GET, target, "/drip")), new Message<>(new BasicHttpRequest(Method.GET, target, "/bytes/20000")), new Message<>(new BasicHttpRequest(Method.GET, target, "/delay/2")), new Message<>( new BasicHttpRequest(Method.POST, target, "/delay/2"), new StringAsyncEntityProducer("some important message", ContentType.TEXT_PLAIN)), new Message<>( new BasicHttpRequest(Method.PUT, target, "/delay/2"), new StringAsyncEntityProducer("some important message", ContentType.TEXT_PLAIN)) ); for (final Message message : requestMessages) { final HttpRequest request = message.getHead(); final AsyncEntityProducer entityProducer = message.getBody(); final Future> messageFuture = client.execute( new BasicRequestProducer(request, entityProducer), new BasicResponseConsumer<>(new StringAsyncEntityConsumer(CharCodingConfig.custom() .setCharset(StandardCharsets.US_ASCII) .setMalformedInputAction(CodingErrorAction.IGNORE) .setUnmappableInputAction(CodingErrorAction.REPLACE) .build())), TIMEOUT, null); final Message response = messageFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertEquals(HttpStatus.SC_OK, response.getHead().getCode()); } } @Test void test_pipelined_request_execution() throws Exception { final HttpAsyncRequester client = client(); final Future connectFuture = client.connect(target, TIMEOUT); final AsyncClientEndpoint streamEndpoint = connectFuture.get(); try { final int n = 20; final CountDownLatch countDownLatch = new CountDownLatch(n); final Queue> resultQueue = new ConcurrentLinkedQueue<>(); for (int i = 0; i < n; i++) { final HttpRequest request; final AsyncEntityProducer entityProducer; if (i % 2 == 0) { request = new BasicHttpRequest(Method.GET, target, "/headers"); entityProducer = null; } else { request = new BasicHttpRequest(Method.POST, target, "/anything"); entityProducer = new StringAsyncEntityProducer("some important message", ContentType.TEXT_PLAIN); } streamEndpoint.execute( new BasicRequestProducer(request, entityProducer), new BasicResponseConsumer<>(new StringAsyncEntityConsumer(CharCodingConfig.custom() .setCharset(StandardCharsets.US_ASCII) .setMalformedInputAction(CodingErrorAction.IGNORE) .setUnmappableInputAction(CodingErrorAction.REPLACE) .build())), new FutureCallback>() { @Override public void completed(final Message responseMessage) { resultQueue.add(new Result<>( request, responseMessage.getHead(), responseMessage.getBody())); countDownLatch.countDown(); } @Override public void failed(final Exception ex) { resultQueue.add(new Result<>(request, ex)); countDownLatch.countDown(); } @Override public void cancelled() { failed(new RequestNotExecutedException()); } }); } Assertions.assertTrue(countDownLatch.await(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()), "Request executions have not completed in time"); for (final Result result : resultQueue) { if (result.isOK()) { Assertions.assertNotNull(result.response); Assertions.assertEquals(HttpStatus.SC_OK, result.response.getCode(), "Response message returned non 200 status"); } else { Assertions.fail(result.exception); } } } finally { streamEndpoint.releaseAndDiscard(); } } } ././@LongLink0100644 0000000 0000000 00000000157 00000250600 011614 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/SocksHttpBinAsyncCompatTest.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/nio/SocksHttpBinAsyncCompa0100644 0000000 0000000 00000003724 00000250600 032656 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.compatibility.nio; import java.net.SocketAddress; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.reactor.IOReactorConfig; public abstract class SocksHttpBinAsyncCompatTest extends HttpBinAsyncCompatTest { public SocksHttpBinAsyncCompatTest(final HttpHost target, final SocketAddress socksProxy, final String socksUser, final String socksPassword) { super(target); configure(bootstrap -> bootstrap .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .setSocksProxyAddress(socksProxy) .setSocksProxyUsername(socksUser) .setSocksProxyPassword(socksPassword) .build()) ); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/0040755 0000000 0000000 00000000000 00000250600 024755 5ustar000000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/ExecutorResource.java0100644 0000000 0000000 00000003765 00000250600 031136 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.extension; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; public class ExecutorResource implements AfterEachCallback { private final ExecutorService executorService; public ExecutorResource(final ExecutorService executorService) { this.executorService = executorService; } public ExecutorResource(final int nThreads) { this.executorService = Executors.newFixedThreadPool(nThreads); } public ExecutorService getExecutorService() { return executorService; } @Override public void afterEach(final ExtensionContext extensionContext) throws Exception { executorService.shutdownNow(); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/SecurityProviderResource.java0100644 0000000 0000000 00000006755 00000250600 032664 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.extension; import java.security.Provider; import java.security.Security; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import org.conscrypt.Conscrypt; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; public class SecurityProviderResource implements BeforeEachCallback, AfterEachCallback { private final String securityProviderName; private Provider securityProvider; public SecurityProviderResource(final String securityProviderName) { super(); this.securityProviderName = securityProviderName; } @Override public void beforeEach(final ExtensionContext context) throws Exception { if ("Conscrypt".equalsIgnoreCase(securityProviderName)) { final Set supportedArchitectures = new HashSet<>(Arrays.asList("x86", "x86_64", "x86-64", "amd64", "aarch64", "armeabi-v7a", "arm64-v8a")); Assumptions.assumeTrue(supportedArchitectures.contains(System.getProperty("os.arch"))); try { securityProvider = Conscrypt.newProviderBuilder().provideTrustManager(true).build(); } catch (final UnsatisfiedLinkError e) { Assertions.fail("Conscrypt provider failed to be loaded: " + e.getMessage()); } } else if ("Oracle".equalsIgnoreCase(securityProviderName)) { securityProvider = null; } else if ("SUN".equalsIgnoreCase(securityProviderName)) { securityProvider = null; } else { throw new AssertionError("Unsupported security provider: " + securityProviderName); } if (securityProvider != null) { Security.insertProviderAt(securityProvider, 1); } } @Override public void afterEach(final ExtensionContext context) throws Exception { if (securityProvider != null) { Security.removeProvider(securityProvider.getName()); securityProvider = null; } } public Provider securityProvider() { return securityProvider != null ? securityProvider : Security.getProvider(securityProviderName); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/classic/0040755 0000000 0000000 00000000000 00000250600 026376 5ustar000000000 0000000 ././@LongLink0100644 0000000 0000000 00000000150 00000250600 011605 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/classic/ClassicTestResources.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/classic/ClassicTestResources.j0100644 0000000 0000000 00000007156 00000250600 032673 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.extension.classic; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.classic.ClassicTestClient; import org.apache.hc.core5.testing.classic.ClassicTestServer; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ClassicTestResources implements BeforeEachCallback, AfterEachCallback { private static final Logger LOG = LoggerFactory.getLogger(ClassicTestResources.class); private final URIScheme scheme; private final Timeout socketTimeout; private ClassicTestServer server; private ClassicTestClient client; public ClassicTestResources(final URIScheme scheme, final Timeout socketTimeout) { this.scheme = scheme; this.socketTimeout = socketTimeout; } @Override public void beforeEach(final ExtensionContext extensionContext) throws Exception { LOG.debug("Starting up test server"); server = new ClassicTestServer( scheme == URIScheme.HTTPS ? SSLTestContexts.createServerSSLContext() : null, SocketConfig.custom() .setSoTimeout(socketTimeout) .build()); LOG.debug("Starting up test client"); client = new ClassicTestClient( scheme == URIScheme.HTTPS ? SSLTestContexts.createClientSSLContext() : null, SocketConfig.custom() .setSoTimeout(socketTimeout) .build()); } @Override public void afterEach(final ExtensionContext extensionContext) throws Exception { LOG.debug("Shutting down test client"); if (client != null) { client.shutdown(CloseMode.IMMEDIATE); } LOG.debug("Shutting down test server"); if (server != null) { server.shutdown(CloseMode.IMMEDIATE); } } public ClassicTestClient client() { Assertions.assertNotNull(client); return client; } public ClassicTestServer server() { Assertions.assertNotNull(server); return server; } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/classic/ExecutorResource.java0100644 0000000 0000000 00000003775 00000250600 032560 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.extension.classic; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; public class ExecutorResource implements AfterEachCallback { private final ExecutorService executorService; public ExecutorResource(final ExecutorService executorService) { this.executorService = executorService; } public ExecutorResource(final int nThreads) { this.executorService = Executors.newFixedThreadPool(nThreads); } public ExecutorService getExecutorService() { return executorService; } @Override public void afterEach(final ExtensionContext extensionContext) throws Exception { executorService.shutdownNow(); } } ././@LongLink0100644 0000000 0000000 00000000151 00000250600 011606 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/classic/HttpRequesterResource.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/classic/HttpRequesterResource.0100644 0000000 0000000 00000005645 00000250600 032735 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.extension.classic; import java.util.function.Consumer; import org.apache.hc.core5.http.impl.bootstrap.HttpRequester; import org.apache.hc.core5.http.impl.bootstrap.RequesterBootstrap; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.testing.classic.LoggingConnPoolListener; import org.apache.hc.core5.testing.classic.LoggingHttp1StreamListener; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class HttpRequesterResource implements AfterEachCallback { private static final Logger LOG = LoggerFactory.getLogger(HttpRequesterResource.class); private final RequesterBootstrap bootstrap; private HttpRequester requester; public HttpRequesterResource() { this.bootstrap = RequesterBootstrap.bootstrap() .setMaxTotal(2) .setDefaultMaxPerRoute(2) .setStreamListener(LoggingHttp1StreamListener.INSTANCE) .setConnPoolListener(LoggingConnPoolListener.INSTANCE); } public void configure(final Consumer customizer) { customizer.accept(bootstrap); } @Override public void afterEach(final ExtensionContext extensionContext) throws Exception { LOG.debug("Shutting down test client"); if (requester != null) { try { requester.close(CloseMode.GRACEFUL); } catch (final Exception ignore) { } } } public HttpRequester start() { if (requester == null) { LOG.debug("Starting up test client"); requester = bootstrap.create(); } return requester; } } ././@LongLink0100644 0000000 0000000 00000000146 00000250600 011612 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/classic/HttpServerResource.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/classic/HttpServerResource.jav0100644 0000000 0000000 00000006016 00000250600 032716 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.extension.classic; import java.io.IOException; import java.net.InetAddress; import java.util.function.Consumer; import org.apache.hc.core5.http.impl.bootstrap.HttpServer; import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.testing.classic.LoggingExceptionListener; import org.apache.hc.core5.testing.classic.LoggingHttp1StreamListener; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class HttpServerResource implements AfterEachCallback { private static final Logger LOG = LoggerFactory.getLogger(HttpServerResource.class); private final ServerBootstrap bootstrap; private HttpServer server; public HttpServerResource() { this.bootstrap = ServerBootstrap.bootstrap() .setLocalAddress(InetAddress.getLoopbackAddress()) .setCanonicalHostName("localhost") .setExceptionListener(LoggingExceptionListener.INSTANCE) .setStreamListener(LoggingHttp1StreamListener.INSTANCE); } public void configure(final Consumer customizer) { customizer.accept(bootstrap); } @Override public void afterEach(final ExtensionContext extensionContext) throws Exception { LOG.debug("Shutting down test server"); if (server != null) { try { server.close(CloseMode.IMMEDIATE); } catch (final Exception ignore) { } } } public HttpServer start() throws IOException { if (server == null) { LOG.debug("Starting up test server"); server = bootstrap.create(); server.start(); } return server; } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/0040755 0000000 0000000 00000000000 00000250600 025542 5ustar000000000 0000000 ././@LongLink0100644 0000000 0000000 00000000150 00000250600 011605 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/H2AsyncRequesterResource.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/H2AsyncRequesterResource.j0100644 0000000 0000000 00000007136 00000250600 032600 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.extension.nio; import java.util.function.Consumer; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2AsyncRequester; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.testing.classic.LoggingConnPoolListener; import org.apache.hc.core5.testing.nio.LoggingExceptionCallback; import org.apache.hc.core5.testing.nio.LoggingH2StreamListener; import org.apache.hc.core5.testing.nio.LoggingHttp1StreamListener; import org.apache.hc.core5.testing.nio.LoggingIOSessionDecorator; import org.apache.hc.core5.testing.nio.LoggingIOSessionListener; import org.apache.hc.core5.testing.nio.LoggingReactorMetricsListener; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class H2AsyncRequesterResource implements AfterEachCallback { private static final Logger LOG = LoggerFactory.getLogger(H2AsyncRequesterResource.class); private final H2RequesterBootstrap bootstrap; private H2AsyncRequester requester; public H2AsyncRequesterResource() { this.bootstrap = H2RequesterBootstrap.bootstrap() .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT) .setStreamListener(LoggingH2StreamListener.INSTANCE) .setConnPoolListener(LoggingConnPoolListener.INSTANCE) .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE) .setExceptionCallback(LoggingExceptionCallback.INSTANCE) .setIOSessionListener(LoggingIOSessionListener.INSTANCE) .setIOReactorMetricsListener(LoggingReactorMetricsListener.INSTANCE); } public void configure(final Consumer customizer) { customizer.accept(bootstrap); } @Override public void afterEach(final ExtensionContext extensionContext) throws Exception { LOG.debug("Shutting down test client"); if (requester != null) { try { requester.close(CloseMode.GRACEFUL); } catch (final Exception ignore) { } } } public H2AsyncRequester start() { if (requester == null) { LOG.debug("Starting up test client"); requester = bootstrap.create(); requester.start(); } return requester; } } ././@LongLink0100644 0000000 0000000 00000000145 00000250600 011611 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/H2AsyncServerResource.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/H2AsyncServerResource.java0100644 0000000 0000000 00000007003 00000250600 032550 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.extension.nio; import java.io.IOException; import java.util.function.Consumer; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2ServerBootstrap; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.testing.nio.LoggingExceptionCallback; import org.apache.hc.core5.testing.nio.LoggingH2StreamListener; import org.apache.hc.core5.testing.nio.LoggingHttp1StreamListener; import org.apache.hc.core5.testing.nio.LoggingIOSessionDecorator; import org.apache.hc.core5.testing.nio.LoggingIOSessionListener; import org.apache.hc.core5.testing.nio.LoggingReactorMetricsListener; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class H2AsyncServerResource implements AfterEachCallback { private static final Logger LOG = LoggerFactory.getLogger(H2AsyncServerResource.class); private final H2ServerBootstrap bootstrap; private HttpAsyncServer server; public H2AsyncServerResource() { this.bootstrap = H2ServerBootstrap.bootstrap() .setCanonicalHostName("localhost") .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER) .setStreamListener(LoggingH2StreamListener.INSTANCE) .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE) .setExceptionCallback(LoggingExceptionCallback.INSTANCE) .setIOSessionListener(LoggingIOSessionListener.INSTANCE) .setIOReactorMetricsListener(LoggingReactorMetricsListener.INSTANCE); } public void configure(final Consumer customizer) { customizer.accept(bootstrap); } @Override public void afterEach(final ExtensionContext extensionContext) throws Exception { LOG.debug("Shutting down test server"); if (server != null) { try { server.close(CloseMode.GRACEFUL); } catch (final Exception ignore) { } } } public HttpAsyncServer start() throws IOException { if (server == null) { LOG.debug("Starting up test server"); server = bootstrap.create(); server.start(); } return server; } } ././@LongLink0100644 0000000 0000000 00000000157 00000250600 011614 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/H2MultiplexingRequesterResource.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/H2MultiplexingRequesterRes0100644 0000000 0000000 00000006634 00000250600 032720 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.extension.nio; import java.util.function.Consumer; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2MultiplexingRequester; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2MultiplexingRequesterBootstrap; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.testing.nio.LoggingExceptionCallback; import org.apache.hc.core5.testing.nio.LoggingH2StreamListener; import org.apache.hc.core5.testing.nio.LoggingIOSessionDecorator; import org.apache.hc.core5.testing.nio.LoggingIOSessionListener; import org.apache.hc.core5.testing.nio.LoggingReactorMetricsListener; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class H2MultiplexingRequesterResource implements AfterEachCallback { private static final Logger LOG = LoggerFactory.getLogger(H2MultiplexingRequesterResource.class); private final H2MultiplexingRequesterBootstrap bootstrap; private H2MultiplexingRequester requester; public H2MultiplexingRequesterResource() { this.bootstrap = H2MultiplexingRequesterBootstrap.bootstrap() .setStreamListener(LoggingH2StreamListener.INSTANCE) .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE) .setExceptionCallback(LoggingExceptionCallback.INSTANCE) .setIOSessionListener(LoggingIOSessionListener.INSTANCE) .setIOReactorMetricsListener(LoggingReactorMetricsListener.INSTANCE); } public void configure(final Consumer customizer) { customizer.accept(bootstrap); } @Override public void afterEach(final ExtensionContext extensionContext) throws Exception { LOG.debug("Shutting down test client"); if (requester != null) { try { requester.close(CloseMode.GRACEFUL); } catch (final Exception ignore) { } } } public H2MultiplexingRequester start() { if (requester == null) { LOG.debug("Starting up test client"); requester = bootstrap.create(); requester.start(); } return requester; } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/H2TestResources.java0100644 0000000 0000000 00000007124 00000250600 031412 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.extension.nio; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.nio.H2TestClient; import org.apache.hc.core5.testing.nio.H2TestServer; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class H2TestResources implements BeforeEachCallback, AfterEachCallback { private static final Logger LOG = LoggerFactory.getLogger(H2TestResources.class); private final URIScheme scheme; private final Timeout socketTimeout; private H2TestServer server; private H2TestClient client; public H2TestResources(final URIScheme scheme, final Timeout socketTimeout) { this.scheme = scheme; this.socketTimeout = socketTimeout; } @Override public void beforeEach(final ExtensionContext extensionContext) throws Exception { LOG.debug("Starting up test server"); server = new H2TestServer( IOReactorConfig.custom() .setSoTimeout(socketTimeout) .build(), scheme == URIScheme.HTTPS ? SSLTestContexts.createServerSSLContext() : null, null, null); LOG.debug("Starting up test client"); client = new H2TestClient( IOReactorConfig.custom() .setSoTimeout(socketTimeout) .build(), scheme == URIScheme.HTTPS ? SSLTestContexts.createClientSSLContext() : null, null, null); } @Override public void afterEach(final ExtensionContext extensionContext) throws Exception { LOG.debug("Shutting down test client"); if (client != null) { client.shutdown(TimeValue.ofSeconds(5)); } LOG.debug("Shutting down test server"); if (server != null) { server.shutdown(TimeValue.ofSeconds(5)); } } public H2TestClient client() { Assertions.assertNotNull(client); return client; } public H2TestServer server() { Assertions.assertNotNull(server); return server; } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/Http1TestResources.java0100644 0000000 0000000 00000007165 00000250600 032146 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.extension.nio; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.nio.Http1TestClient; import org.apache.hc.core5.testing.nio.Http1TestServer; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Http1TestResources implements BeforeEachCallback, AfterEachCallback { private static final Logger LOG = LoggerFactory.getLogger(Http1TestResources.class); private final URIScheme scheme; private final Timeout socketTimeout; private Http1TestServer server; private Http1TestClient client; public Http1TestResources(final URIScheme scheme, final Timeout socketTimeout) { this.scheme = scheme; this.socketTimeout = socketTimeout; } @Override public void beforeEach(final ExtensionContext extensionContext) throws Exception { LOG.debug("Starting up test server"); server = new Http1TestServer( IOReactorConfig.custom() .setSoTimeout(socketTimeout) .build(), scheme == URIScheme.HTTPS ? SSLTestContexts.createServerSSLContext() : null, null, null); LOG.debug("Starting up test client"); client = new Http1TestClient( IOReactorConfig.custom() .setSoTimeout(socketTimeout) .build(), scheme == URIScheme.HTTPS ? SSLTestContexts.createClientSSLContext() : null, null, null); } @Override public void afterEach(final ExtensionContext extensionContext) throws Exception { LOG.debug("Shutting down test client"); if (client != null) { client.shutdown(TimeValue.ofSeconds(5)); } LOG.debug("Shutting down test server"); if (server != null) { server.shutdown(TimeValue.ofSeconds(5)); } } public Http1TestClient client() { Assertions.assertNotNull(client); return client; } public Http1TestServer server() { Assertions.assertNotNull(server); return server; } } ././@LongLink0100644 0000000 0000000 00000000152 00000250600 011607 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/HttpAsyncRequesterResource.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/HttpAsyncRequesterResource0100644 0000000 0000000 00000006650 00000250600 033016 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.extension.nio; import java.util.function.Consumer; import org.apache.hc.core5.http.impl.bootstrap.AsyncRequesterBootstrap; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.testing.classic.LoggingConnPoolListener; import org.apache.hc.core5.testing.nio.LoggingHttp1StreamListener; import org.apache.hc.core5.testing.nio.LoggingIOSessionDecorator; import org.apache.hc.core5.testing.nio.LoggingIOSessionListener; import org.apache.hc.core5.testing.nio.LoggingReactorMetricsListener; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class HttpAsyncRequesterResource implements AfterEachCallback { private static final Logger LOG = LoggerFactory.getLogger(HttpAsyncRequesterResource.class); private final AsyncRequesterBootstrap bootstrap; private HttpAsyncRequester requester; public HttpAsyncRequesterResource() { this.bootstrap = AsyncRequesterBootstrap.bootstrap() .setMaxTotal(2) .setDefaultMaxPerRoute(2) .setIOSessionListener(LoggingIOSessionListener.INSTANCE) .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT) .setConnPoolListener(LoggingConnPoolListener.INSTANCE) .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE) .setIOReactorMetricsListener(LoggingReactorMetricsListener.INSTANCE); } public void configure(final Consumer customizer) { customizer.accept(bootstrap); } @Override public void afterEach(final ExtensionContext extensionContext) throws Exception { LOG.debug("Shutting down test client"); if (requester != null) { try { requester.close(CloseMode.GRACEFUL); } catch (final Exception ignore) { } } } public HttpAsyncRequester start() { if (requester == null) { LOG.debug("Starting up test client"); requester = bootstrap.create(); requester.start(); } return requester; } } ././@LongLink0100644 0000000 0000000 00000000147 00000250600 011613 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/HttpAsyncServerResource.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/HttpAsyncServerResource.ja0100644 0000000 0000000 00000006614 00000250600 032676 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.extension.nio; import java.io.IOException; import java.util.function.Consumer; import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.testing.nio.LoggingExceptionCallback; import org.apache.hc.core5.testing.nio.LoggingHttp1StreamListener; import org.apache.hc.core5.testing.nio.LoggingIOSessionDecorator; import org.apache.hc.core5.testing.nio.LoggingIOSessionListener; import org.apache.hc.core5.testing.nio.LoggingReactorMetricsListener; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class HttpAsyncServerResource implements AfterEachCallback { private static final Logger LOG = LoggerFactory.getLogger(HttpAsyncServerResource.class); private final AsyncServerBootstrap bootstrap; private HttpAsyncServer server; public HttpAsyncServerResource() { this.bootstrap = AsyncServerBootstrap.bootstrap() .setCanonicalHostName("localhost") .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER) .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE) .setExceptionCallback(LoggingExceptionCallback.INSTANCE) .setIOSessionListener(LoggingIOSessionListener.INSTANCE) .setIOReactorMetricsListener(LoggingReactorMetricsListener.INSTANCE); } public void configure(final Consumer customizer) { customizer.accept(bootstrap); } @Override public void afterEach(final ExtensionContext extensionContext) throws Exception { LOG.debug("Shutting down test server"); if (server != null) { try { server.close(CloseMode.GRACEFUL); } catch (final Exception ignore) { } } } public HttpAsyncServer start() throws IOException { if (server == null) { LOG.debug("Starting up test server"); server = bootstrap.create(); server.start(); } return server; } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/0040755 0000000 0000000 00000000000 00000250600 024736 5ustar000000000 0000000 ././@LongLink0100644 0000000 0000000 00000000157 00000250600 011614 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestClassicTestClientTestingAdapter.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestClassicTestClientTestingAd0100644 0000000 0000000 00000025155 00000250600 032711 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.framework; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.BODY; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.HEADERS; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.METHOD; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.PATH; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.STATUS; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.testing.classic.ClassicTestServer; import org.apache.hc.core5.testing.classic.EchoHandler; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; class TestClassicTestClientTestingAdapter { private static final String ECHO_PATH = "echo/something"; private static final String CUSTOM_PATH = "custom/something"; private ClassicTestServer server; @BeforeEach void initServer() { this.server = new ClassicTestServer(SocketConfig.custom() .setSoTimeout(5, TimeUnit.SECONDS).build()); } @AfterEach void shutDownServer() { if (this.server != null) { this.server.shutdown(CloseMode.IMMEDIATE); } } @Test void nullDefaultURI() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final String defaultURI = null; final Map request = new HashMap<>(); final TestingFrameworkRequestHandler requestHandler = Mockito.mock(TestingFrameworkRequestHandler.class); final Map responseExpectations = new HashMap<>(); Assertions.assertThrows(TestingFrameworkException.class, () -> adapter.execute(defaultURI, request, requestHandler, responseExpectations)); } @Test void nullRequest() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final String defaultURI = ""; final Map request = null; final TestingFrameworkRequestHandler requestHandler = Mockito.mock(TestingFrameworkRequestHandler.class); final Map responseExpectations = new HashMap<>(); Assertions.assertThrows(TestingFrameworkException.class, () -> adapter.execute(defaultURI, request, requestHandler, responseExpectations)); } @Test void nullRequestHandler() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final String defaultURI = ""; final Map request = new HashMap<>(); final TestingFrameworkRequestHandler requestHandler = null; final Map responseExpectations = new HashMap<>(); Assertions.assertThrows(TestingFrameworkException.class, () -> adapter.execute(defaultURI, request, requestHandler, responseExpectations)); } @Test void nullResponseExpectations() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final String defaultURI = ""; final Map request = new HashMap<>(); final TestingFrameworkRequestHandler requestHandler = Mockito.mock(TestingFrameworkRequestHandler.class); final Map responseExpectations = null; Assertions.assertThrows(TestingFrameworkException.class, () -> adapter.execute(defaultURI, request, requestHandler, responseExpectations)); } @Test void noPath() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final String defaultURI = ""; final Map request = new HashMap<>(); final TestingFrameworkRequestHandler requestHandler = Mockito.mock(TestingFrameworkRequestHandler.class); final Map responseExpectations = new HashMap<>(); Assertions.assertThrows(TestingFrameworkException.class, () -> adapter.execute(defaultURI, request, requestHandler, responseExpectations)); } @Test void noMethod() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final String defaultURI = ""; final Map request = new HashMap<>(); request.put(PATH, ECHO_PATH); final TestingFrameworkRequestHandler requestHandler = Mockito.mock(TestingFrameworkRequestHandler.class); final Map responseExpectations = new HashMap<>(); Assertions.assertThrows(TestingFrameworkException.class, () -> adapter.execute(defaultURI, request, requestHandler, responseExpectations)); } @Test void invalidMethod() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final String defaultURI = ""; final Map request = new HashMap<>(); request.put(PATH, ECHO_PATH); request.put(METHOD, "JUNK"); final TestingFrameworkRequestHandler requestHandler = Mockito.mock(TestingFrameworkRequestHandler.class); final Map responseExpectations = new HashMap<>(); Assertions.assertThrows(TestingFrameworkException.class, () -> adapter.execute(defaultURI, request, requestHandler, responseExpectations)); } @Test void withLiveServerEcho() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); // Initialize the server-side request handler server.register("/echo/*", new EchoHandler()); this.server.start(); final HttpHost target = new HttpHost("localhost", this.server.getPort()); final String defaultURI = target.toString(); final Map request = new HashMap<>(); request.put(PATH, ECHO_PATH); request.put(METHOD, Method.POST.name()); final String body = "mybody"; request.put(BODY, body); final Map responseExpectations = new HashMap<>(); final TestingFrameworkRequestHandler requestHandler = Mockito.mock(TestingFrameworkRequestHandler.class); final Map response = adapter.execute(defaultURI, request, requestHandler, responseExpectations); Assertions.assertNotNull(response, "response should not be null"); Assertions.assertEquals(200, response.get(STATUS), "status unexpected"); @SuppressWarnings("unchecked") final Map headers = (Map) response.get(HEADERS); Assertions.assertNotNull(headers, "headers should be in the response"); Assertions.assertFalse(headers.isEmpty()); final String returnedBody = (String) response.get(BODY); Assertions.assertNotNull(returnedBody, "body should be in the response"); Assertions.assertEquals(body, returnedBody, "Body should be echoed"); } @Test void withLiveServerCustomRequestHandler() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final TestingFrameworkRequestHandler requestHandler = new TestingFrameworkRequestHandler() { @Override public void handle(final ClassicHttpRequest request, final ClassicHttpResponse response, final HttpContext context) throws HttpException, IOException { try { Assertions.assertEquals("junk", request.getMethod(), "method not expected"); } catch (final Throwable t) { thrown = t; } } }; server.register("/custom/*", requestHandler); this.server.start(); final HttpHost target = new HttpHost("localhost", this.server.getPort()); final String defaultURI = target.toString(); final Map responseExpectations = new HashMap<>(); final Map request = new HashMap<>(); request.put(PATH, CUSTOM_PATH); for (final String method : TestingFramework.ALL_METHODS) { request.put(METHOD, method); adapter.execute(defaultURI, request, requestHandler, responseExpectations); } } @Test void modifyRequest() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final Map request = new HashMap<>(); final Map returnedRequest = adapter.modifyRequest(request); Assertions.assertSame(request, returnedRequest, "Same request was not returned as expected."); } @Test void modifyResponseExpectations() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final Map responseExpectations = new HashMap<>(); final Map returnedResponseExpectations = adapter.modifyResponseExpectations(null, responseExpectations); Assertions.assertSame(responseExpectations, returnedResponseExpectations, "Same response expectations were not returned as expected."); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestClientPojoAdapter.java0100644 0000000 0000000 00000005332 00000250600 032010 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.framework; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestClientPojoAdapter { @Test void modifyRequest() { final ClientPOJOAdapter adapter = new ClassicTestClientAdapter(); final Map request = new HashMap<>(); final Map request2 = adapter.modifyRequest(request); Assertions.assertSame(request, request2, "request should have been returned"); } @Test void checkRequestSupport() throws Exception { final ClientPOJOAdapter adapter = new ClassicTestClientAdapter(); final String reason = adapter.checkRequestSupport(null); Assertions.assertNull(reason, "reason should be null"); adapter.assertRequestSupported(null); } @Test void checkRequestSupportThrows() { final ClientPOJOAdapter adapter = new ClientPOJOAdapter() { @Override public Map execute(final String defaultURI, final Map request) throws Exception { return null; } @Override public String checkRequestSupport(final java.util.Map request) { return "A reason this request is not supported."; } @Override public String getClientName() { return null; } }; Assertions.assertThrows(Exception.class, () -> adapter.assertRequestSupported(null)); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestClientTestingAdapter.java0100644 0000000 0000000 00000003651 00000250600 032520 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.framework; import java.util.Map; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestClientTestingAdapter { @Test void getHttpClientPOJOAdapter() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final ClientPOJOAdapter pojoAdapter = adapter.getClientPOJOAdapter(); Assertions.assertNotNull(pojoAdapter, "pojoAdapter should not be null"); } @Test void isRequestSupported() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final Map request = null; Assertions.assertTrue(adapter.isRequestSupported(request), "isRequestSupported should return true"); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestFrameworkTest.java0100644 0000000 0000000 00000011765 00000250600 031245 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.framework; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.BODY; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.CONTENT_TYPE; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.HEADERS; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.METHOD; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.PROTOCOL_VERSION; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.QUERY; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.REQUEST; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.RESPONSE; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.STATUS; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestFrameworkTest { @Test void defaults() throws Exception { final FrameworkTest test = new FrameworkTest(); final Map request = test.initRequest(); Assertions.assertNotNull(request, "request should not be null"); Assertions.assertEquals("GET", request.get(METHOD), "Default method should be GET"); Assertions.assertEquals(TestingFramework.DEFAULT_REQUEST_BODY, request.get(BODY), "Default request body expected."); Assertions.assertEquals(TestingFramework.DEFAULT_REQUEST_CONTENT_TYPE, request.get(CONTENT_TYPE), "Default request content type expected."); Assertions.assertEquals(TestingFramework.DEFAULT_REQUEST_QUERY, request.get(QUERY), "Default request query parameters expected."); Assertions.assertEquals(TestingFramework.DEFAULT_REQUEST_HEADERS, request.get(HEADERS), "Default request headers expected."); Assertions.assertEquals(TestingFramework.DEFAULT_REQUEST_PROTOCOL_VERSION, request.get(PROTOCOL_VERSION), "Default protocol version expected."); final Map responseExpectations = test.initResponseExpectations(); Assertions.assertNotNull(responseExpectations, "responseExpectations should not be null"); Assertions.assertEquals(TestingFramework.DEFAULT_RESPONSE_STATUS, responseExpectations.get(STATUS), "Default status expected."); Assertions.assertEquals(TestingFramework.DEFAULT_RESPONSE_BODY, responseExpectations.get(BODY), "Default body expected."); Assertions.assertEquals(TestingFramework.DEFAULT_RESPONSE_CONTENT_TYPE, responseExpectations.get(CONTENT_TYPE), "Default response content type expected."); Assertions.assertEquals(TestingFramework.DEFAULT_RESPONSE_HEADERS, responseExpectations.get(HEADERS), "Default headers expected."); } @Test void changeStatus() { final Map testMap = new HashMap<>(); final Map response = new HashMap<>(); testMap.put(RESPONSE, response); response.put(STATUS, 201); final FrameworkTest test = new FrameworkTest(testMap); final Map responseExpectations = test.initResponseExpectations(); Assertions.assertEquals(201, responseExpectations.get(STATUS), "Status unexpected."); } @Test void changeMethod() throws Exception { final Map testMap = new HashMap<>(); final Map request = new HashMap<>(); testMap.put(REQUEST, request); request.put(METHOD, "POST"); final FrameworkTest test = new FrameworkTest(testMap); final Map requestExpectations = test.initRequest(); Assertions.assertEquals("POST", requestExpectations.get(METHOD), "Method unexpected."); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestTestingFramework.java0100644 0000000 0000000 00000125214 00000250600 031736 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.framework; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.BODY; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.CONTENT_TYPE; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.HEADERS; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.METHOD; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.NAME; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.PATH; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.QUERY; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.REQUEST; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.RESPONSE; import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.STATUS; import static org.hamcrest.MatcherAssert.assertThat; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.hc.core5.http.ContentType; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; class TestTestingFramework { @Test void ensureDefaultMapsUnmodifiable() { assertUnmodifiable(TestingFramework.DEFAULT_REQUEST_QUERY); assertUnmodifiable(TestingFramework.DEFAULT_RESPONSE_HEADERS); } private void assertUnmodifiable(final Map map) { final String aKey = (String) map.keySet().toArray()[0]; Assertions.assertThrows(UnsupportedOperationException.class, () -> map.remove(aKey)); } private TestingFramework newWebServerTestingFramework(final ClientTestingAdapter adapter) throws TestingFrameworkException { final TestingFramework framework = new TestingFramework(adapter); // get rid of the default tests. framework.deleteTests(); return framework; } private TestingFramework newWebServerTestingFramework() throws TestingFrameworkException { return newWebServerTestingFramework(null); // null adapter } @Test void runTestsWithoutSettingAdapterThrows() throws Exception { final TestingFramework framework = newWebServerTestingFramework(); Assertions.assertThrows(TestingFrameworkException.class, () -> framework.runTests()); } @Test void nullAdapterThrows() throws Exception { final ClientTestingAdapter adapter = null; final TestingFramework framework = newWebServerTestingFramework(adapter); Assertions.assertThrows(TestingFrameworkException.class, () -> framework.runTests()); } @Test void nullSetAdapterThrows() throws Exception { final ClientTestingAdapter adapter = null; final TestingFramework framework = newWebServerTestingFramework(adapter); framework.setAdapter(adapter); Assertions.assertThrows(TestingFrameworkException.class, () -> framework.runTests()); } @Test void goodAdapterWithConstructor() throws Exception { final ClientTestingAdapter adapter = Mockito.mock(ClientTestingAdapter.class); // Have isRequestSupported() return false so no test will run. Mockito.when(adapter.isRequestSupported(ArgumentMatchers.anyMap())) .thenReturn(false); final TestingFramework framework = newWebServerTestingFramework(adapter); framework.runTests(); // since there are no tests, callMethod should not be called. verifyCallMethodNeverCalled(adapter); } private void verifyCallMethodNeverCalled(final ClientTestingAdapter adapter) throws Exception { Mockito.verify(adapter, Mockito.never()).execute(ArgumentMatchers.anyString(), ArgumentMatchers.anyMap(), ArgumentMatchers.any(TestingFrameworkRequestHandler.class), ArgumentMatchers.anyMap()); } private TestingFramework newFrameworkAndSetAdapter(final ClientTestingAdapter adapter) throws TestingFrameworkException { final TestingFramework framework = new TestingFramework(); framework.setAdapter(adapter); // get rid of the default tests. framework.deleteTests(); return framework; } @Test void goodAdapterWithSetter() throws Exception { final ClientTestingAdapter adapter = Mockito.mock(ClientTestingAdapter.class); final TestingFramework framework = newFrameworkAndSetAdapter(adapter); framework.runTests(); // since there are no tests, callMethod should not be called. verifyCallMethodNeverCalled(adapter); } @Test void addTest() throws Exception { final TestingFrameworkRequestHandler mockRequestHandler = Mockito.mock(TestingFrameworkRequestHandler.class); final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) throws TestingFrameworkException { assertThat(defaultURI, matchesDefaultURI()); Assertions.assertNotNull(request, "request should not be null"); // The request should be equal to the default request. final Map defaultRequest = new FrameworkTest().initRequest(); Assertions.assertEquals(defaultRequest, request, "The request does not match the default"); Assertions.assertSame(mockRequestHandler, requestHandler, "The request handler should have been passed to the adapter"); // The responseExpectations should be equal to the default. final Map defaultResponseExpectations = new FrameworkTest().initResponseExpectations(); Assertions.assertEquals(defaultResponseExpectations, responseExpectations, "The responseExpectations do not match the defaults"); final Map response = new HashMap<>(); response.put(STATUS, responseExpectations.get(STATUS)); response.put(BODY, responseExpectations.get(BODY)); response.put(CONTENT_TYPE, responseExpectations.get(CONTENT_TYPE)); response.put(HEADERS, responseExpectations.get(HEADERS)); return response; } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); framework.setRequestHandler(mockRequestHandler); framework.addTest(); framework.runTests(); // assertNothingThrown() should have been called. Mockito.verify(mockRequestHandler).assertNothingThrown(); } private Matcher matchesDefaultURI() { final Matcher matcher = new BaseMatcher() { private final String regex = "http://localhost:\\d+/"; @Override public boolean matches(final Object o) { return ((String) o).matches(regex); } @Override public void describeTo(final Description description) { description.appendText("matches regex=" + regex); } }; return matcher; } @Test void statusCheck() throws Exception { final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) { Assertions.assertEquals(200, responseExpectations.get(STATUS)); // return a different status than expected. final Map response = new HashMap<>(); response.put(STATUS, 201); return response; } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); framework.addTest(); Assertions.assertThrows(TestingFrameworkException.class, () -> framework.runTests()); } private Map alreadyCheckedResponse() { // return an indication that the response has already been checked. final Map response = new HashMap<>(); response.put(STATUS, TestingFramework.ALREADY_CHECKED); response.put(BODY, TestingFramework.ALREADY_CHECKED); response.put(CONTENT_TYPE, TestingFramework.ALREADY_CHECKED); response.put(HEADERS, TestingFramework.ALREADY_CHECKED); return response; } @Test void responseAlreadyChecked() throws Exception { final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) { return alreadyCheckedResponse(); } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); framework.addTest(); framework.runTests(); } @Test void bodyCheck() throws Exception { final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) { Assertions.assertEquals(TestingFramework.DEFAULT_RESPONSE_BODY, responseExpectations.get(BODY)); final Map response = new HashMap<>(); response.put(STATUS, TestingFramework.ALREADY_CHECKED); // return a different body than expected. response.put(BODY, TestingFramework.DEFAULT_RESPONSE_BODY + "junk"); return response; } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); framework.addTest(); Assertions.assertThrows(TestingFrameworkException.class, () -> framework.runTests()); } @Test void responseContentTypeCheck() throws Exception { final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) { Assertions.assertEquals(TestingFramework.DEFAULT_RESPONSE_CONTENT_TYPE, responseExpectations.get(CONTENT_TYPE)); final Map response = new HashMap<>(); response.put(STATUS, TestingFramework.ALREADY_CHECKED); response.put(HEADERS, TestingFramework.ALREADY_CHECKED); // return the expected body response.put(BODY, TestingFramework.DEFAULT_RESPONSE_BODY); // return a different content type than expected. response.put(CONTENT_TYPE, ContentType.DEFAULT_TEXT); return response; } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); framework.addTest(); Assertions.assertThrows(TestingFrameworkException.class, () -> framework.runTests()); } @Test void deepcopy() throws Exception { // save a copy of the headers to make sure they haven't changed at the end of this test. @SuppressWarnings("unchecked") final Map headersCopy = (Map) TestingFramework.deepcopy(TestingFramework.DEFAULT_RESPONSE_HEADERS); Assertions.assertEquals(TestingFramework.DEFAULT_RESPONSE_HEADERS, headersCopy); final Map deepMap = new HashMap<>(); deepMap.put(HEADERS, TestingFramework.DEFAULT_RESPONSE_HEADERS); @SuppressWarnings("unchecked") final Map deepMapCopy = (Map) TestingFramework.deepcopy(deepMap); Assertions.assertEquals(deepMap, deepMapCopy); @SuppressWarnings("unchecked") final Map headersMap = (Map) deepMapCopy.get(HEADERS); Assertions.assertEquals(headersCopy, headersMap); // now make sure the default headers have not changed for some unexpected reason. Assertions.assertEquals(TestingFramework.DEFAULT_RESPONSE_HEADERS, headersCopy); } @Test void removedHeaderCheck() throws Exception { final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) { Assertions.assertEquals(TestingFramework.DEFAULT_RESPONSE_HEADERS, responseExpectations.get(HEADERS)); @SuppressWarnings("unchecked") final Map headersCopy = (Map) deepcopy(responseExpectations.get(HEADERS)); // remove a header to force an error final String headerName = (String) headersCopy.keySet().toArray()[0]; headersCopy.remove(headerName); final Map response = new HashMap<>(); response.put(STATUS, TestingFramework.ALREADY_CHECKED); response.put(BODY, TestingFramework.ALREADY_CHECKED); // return different headers than expected. response.put(HEADERS, headersCopy); return response; } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); framework.addTest(); Assertions.assertThrows(TestingFrameworkException.class, () -> framework.runTests()); } @Test void changedHeaderCheck() throws Exception { final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) { Assertions.assertEquals(TestingFramework.DEFAULT_RESPONSE_HEADERS, responseExpectations.get(HEADERS)); @SuppressWarnings("unchecked") final Map headersCopy = (Map) deepcopy(responseExpectations.get(HEADERS)); // change a header to force an error final String headerName = (String) headersCopy.keySet().toArray()[0]; headersCopy.put(headerName, headersCopy.get(headerName) + "junk"); final Map response = new HashMap<>(); response.put(STATUS, TestingFramework.ALREADY_CHECKED); response.put(BODY, TestingFramework.ALREADY_CHECKED); // return different headers than expected. response.put(HEADERS, headersCopy); return response; } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); framework.addTest(); Assertions.assertThrows(TestingFrameworkException.class, () -> framework.runTests()); } private Object deepcopy(final Object obj) { try { return TestingFramework.deepcopy(obj); } catch (final Exception e) { Assertions.fail("deepcopy failed: " + e.getMessage()); return null; } } @Test void requestMethodUnexpected() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) throws TestingFrameworkException { // change the request from what is expected. Assertions.assertEquals("GET", request.get(METHOD)); request.put(METHOD, "POST"); return super.execute(defaultURI, request, requestHandler, responseExpectations); } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); final Map test = new HashMap<>(); final Map request = new HashMap<>(); test.put(REQUEST, request); request.put(NAME, "MyName"); framework.addTest(test); final TestingFrameworkException exception = Assertions.assertThrows(TestingFrameworkException.class, () -> framework.runTests()); // make sure the HTTP Client name is in the message. final String message = exception.getMessage(); final ClientPOJOAdapter pojoAdapter = adapter.getClientPOJOAdapter(); final String httpClientName = pojoAdapter == null ? TestingFrameworkException.NO_HTTP_CLIENT : pojoAdapter.getClientName(); Assertions.assertTrue(message.contains(httpClientName), "Message should contain httpClientName of " + httpClientName + "; message=" + message); Assertions.assertTrue(message.contains("MyName"), "Message should contain the test. message=" + message); } @Test void status201() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final TestingFramework framework = newFrameworkAndSetAdapter(adapter); final Map test = new HashMap<>(); final Map response = new HashMap<>(); test.put(RESPONSE, response); response.put(STATUS, 201); framework.addTest(test); framework.runTests(); } @Test void deepcopyOfTest() throws Exception { final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) throws TestingFrameworkException { Assertions.assertEquals(201, responseExpectations.get(STATUS)); return alreadyCheckedResponse(); } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); final Map test = new HashMap<>(); final Map response = new HashMap<>(); test.put(RESPONSE, response); response.put(STATUS, 201); framework.addTest(test); // Make sure the framework makes a copy of the test for itself. // This put should be ignored by the framework. response.put(STATUS, 300); framework.runTests(); } @Test void removeParameter() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) throws TestingFrameworkException { // change the request from what is expected. @SuppressWarnings("unchecked") final Map query = (Map) request.get(QUERY); Assertions.assertTrue(query.containsKey("p1")); query.remove("p1"); return super.execute(defaultURI, request, requestHandler, responseExpectations); } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); framework.addTest(); Assertions.assertThrows(TestingFrameworkException.class, () -> framework.runTests()); } @Test void changeParameter() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) throws TestingFrameworkException { // change the request from what is expected. @SuppressWarnings("unchecked") final Map query = (Map) request.get(QUERY); Assertions.assertTrue(query.containsKey("p1")); query.put("p1", query.get("p1") + "junk"); return super.execute(defaultURI, request, requestHandler, responseExpectations); } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); framework.addTest(); Assertions.assertThrows(TestingFrameworkException.class, () -> framework.runTests()); } @Test void removeHeader() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) throws TestingFrameworkException { // change the request from what is expected. @SuppressWarnings("unchecked") final Map headers = (Map) request.get(HEADERS); Assertions.assertTrue(headers.containsKey("header1")); headers.remove("header1"); return super.execute(defaultURI, request, requestHandler, responseExpectations); } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); framework.addTest(); Assertions.assertThrows(TestingFrameworkException.class, () -> framework.runTests()); } @Test void changeHeader() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) throws TestingFrameworkException { // change the request from what is expected. @SuppressWarnings("unchecked") final Map headers = (Map) request.get(HEADERS); Assertions.assertTrue(headers.containsKey("header1")); headers.put("header1", headers.get("header1") + "junk"); return super.execute(defaultURI, request, requestHandler, responseExpectations); } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); framework.addTest(); Assertions.assertThrows(TestingFrameworkException.class, () -> framework.runTests()); } @Test void changeBody() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) throws TestingFrameworkException { // change the request from what is expected. final String body = (String) request.get(BODY); Assertions.assertNotNull(body); request.put(BODY, request.get(BODY) + "junk"); return super.execute(defaultURI, request, requestHandler, responseExpectations); } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); framework.addTest(); Assertions.assertThrows(TestingFrameworkException.class, () -> framework.runTests()); } @Test void changeContentType() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) throws TestingFrameworkException { // change the request from what is expected. final String contentType = (String) request.get(CONTENT_TYPE); Assertions.assertNotNull(contentType); request.put(CONTENT_TYPE, request.get(CONTENT_TYPE) + "junk"); return super.execute(defaultURI, request, requestHandler, responseExpectations); } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); framework.addTest(); Assertions.assertThrows(TestingFrameworkException.class, () -> framework.runTests()); } @Test void changeResponseExpectationsFails() throws Exception { final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) throws TestingFrameworkException { /* * The adapter should change the responseExpectations in the modifyResponseExpectations() * method before they are sent to the request handler. The expectations should not * be changed here. */ // the next command should throw. responseExpectations.put(STATUS, 201); return null; } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); framework.addTest(); Assertions.assertThrows(TestingFrameworkException.class, () -> framework.runTests()); } @Test void changeResponseStatus() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) throws TestingFrameworkException { // change the responseExpectations from what is expected. The change should be ignored // by the request handler, and a 200 should actually be returned. Assertions.assertEquals(200, responseExpectations.get(STATUS)); // The next line is needed because we have to make a copy of the responseExpectations. // It is an unmodifiable map. final Map tempResponseExpectations = new HashMap<>(responseExpectations); tempResponseExpectations.put(STATUS, 201); final Map response = super.execute(defaultURI, request, requestHandler, tempResponseExpectations); Assertions.assertEquals(200, response.get(STATUS)); return response; } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); framework.addTest(); framework.runTests(); } @Test void modifyRequestCalled() throws Exception { final TestingFrameworkRequestHandler mockRequestHandler = Mockito.mock(TestingFrameworkRequestHandler.class); final String UNLIKELY_ITEM = "something_unlikely_to_be_in_a_real_request"; final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) throws TestingFrameworkException { // make sure the modifyRequest method was called by seeing if the request was modified. Assertions.assertTrue(request.containsKey(UNLIKELY_ITEM), "modifyRequest should have been called."); final Map response = new HashMap<>(); response.put(STATUS, responseExpectations.get(STATUS)); response.put(BODY, responseExpectations.get(BODY)); response.put(CONTENT_TYPE, responseExpectations.get(CONTENT_TYPE)); response.put(HEADERS, responseExpectations.get(HEADERS)); return response; } @Override public Map modifyRequest(final Map request) { // let the adapter change the request if needed. request.put(UNLIKELY_ITEM, new Object()); return super.modifyRequest(request); } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); framework.setRequestHandler(mockRequestHandler); framework.addTest(); framework.runTests(); // assertNothingThrown() should have been called. Mockito.verify(mockRequestHandler).assertNothingThrown(); } @Test void modifyResponseExpectationsCalled() throws Exception { final TestingFrameworkRequestHandler mockRequestHandler = Mockito.mock(TestingFrameworkRequestHandler.class); final String UNLIKELY_ITEM = "something_unlikely_to_be_in_a_real_response"; final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) throws TestingFrameworkException { // make sure the modifyRequest method was called by seeing if the request was modified. Assertions.assertTrue(responseExpectations.containsKey(UNLIKELY_ITEM), "modifyResponseExpectations should have been called."); final Map response = new HashMap<>(); response.put(STATUS, responseExpectations.get(STATUS)); response.put(BODY, responseExpectations.get(BODY)); response.put(CONTENT_TYPE, responseExpectations.get(CONTENT_TYPE)); response.put(HEADERS, responseExpectations.get(HEADERS)); return response; } @Override public Map modifyResponseExpectations( final Map request, final Map responseExpectations) { // let the adapter change the request if needed. responseExpectations.put(UNLIKELY_ITEM, new Object()); return super.modifyResponseExpectations(request, responseExpectations); } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); framework.setRequestHandler(mockRequestHandler); framework.addTest(); framework.runTests(); // assertNothingThrown() should have been called. Mockito.verify(mockRequestHandler).assertNothingThrown(); } @Test void adapterDoesNotSupport() throws Exception { final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) throws TestingFrameworkException { Assertions.fail("callMethod should not have been called"); return null; } @Override public boolean isRequestSupported(final Map request) { return false; } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); framework.addTest(); framework.runTests(); } @Test void defaultTestsWithMockedAdapter() throws Exception { final Set calledMethodSet = new HashSet<>(); final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) throws TestingFrameworkException { calledMethodSet.add((String) request.get(METHOD)); return alreadyCheckedResponse(); } }; // create the framework without deleting the default tests. final TestingFramework framework = new TestingFramework(); framework.setAdapter(adapter); framework.runTests(); for (final String method : TestingFramework.ALL_METHODS) { Assertions.assertTrue(calledMethodSet.contains(method), "Method not in default tests. method=" + method); } } @Test void defaultTests() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); // create the framework without deleting the default tests. final TestingFramework framework = new TestingFramework(); framework.setAdapter(adapter); framework.runTests(); } @Test void addTestNoMocks() throws TestingFrameworkException { final TestingFramework framework = new TestingFramework(new ClassicTestClientTestingAdapter()); // The following test could be constructed in Groovy/Spock like this: // // HttpServerTestingFramework.ALL_METHODS.each { method -> // framework.addTest( // request: [ // path: '/stuff', // method:method, // query: [param : 'something'], // headers: [header1:'stuff', header2:'more-stuff'], // contentType: 'text/plain; charset=us-ascii', // body: 'What is the meaning of life?', // ], // response: [ // status:201, // headers: [header3:'header_stuff',], // contentType: 'text/html; charset=us-ascii', // body: responseBody, // ], // ) final Map test = new HashMap<>(); // Add request. final Map request = new HashMap<>(); test.put(REQUEST, request); request.put(PATH, "stuff"); final Map queryMap = new HashMap<>(); request.put(QUERY, queryMap); queryMap.put("param", "something"); final Map requestHeadersMap = new HashMap<>(); request.put(HEADERS, requestHeadersMap); requestHeadersMap.put("header1", "stuff"); requestHeadersMap.put("header2", "more-stuff"); request.put(CONTENT_TYPE, "text/plain; charset=us-ascii"); request.put(BODY, "What is the meaning of life?"); // Response final Map response = new HashMap<>(); test.put(RESPONSE, response); response.put(STATUS, 201); final Map responseHeadersMap = new HashMap<>(); response.put(HEADERS, responseHeadersMap); responseHeadersMap.put("header3", "header_stuff"); response.put(CONTENT_TYPE, "text/html; charset=us-ascii"); response.put(BODY, "42"); for (final String method : TestingFramework.ALL_METHODS) { request.put(METHOD, method); framework.addTest(test); } framework.runTests(); } @Test void nulls() throws TestingFrameworkException { final TestingFramework framework = new TestingFramework(new ClassicTestClientTestingAdapter()); // The following test could be constructed in Groovy/Spock like this: // // WebServerTestingFramework.ALL_METHODS.each { method -> // framework.addTest( // request: [ // path: null, // method:method, // query: null, // headers: null, // contentType: null, // body: null, // ], // response: [ // status:null, // headers: null, // contentType: null, // body: null, // ], // ) final Map test = new HashMap<>(); // Add request. final Map request = new HashMap<>(); test.put(REQUEST, request); request.put(PATH, null); request.put(QUERY, null); request.put(HEADERS, null); request.put(CONTENT_TYPE, null); request.put(BODY, null); // Response final Map response = new HashMap<>(); test.put(RESPONSE, response); response.put(STATUS, null); response.put(HEADERS, null); response.put(CONTENT_TYPE, null); response.put(BODY, null); for (final String method : TestingFramework.ALL_METHODS) { request.put(METHOD, method); framework.addTest(test); } framework.runTests(); } @Test void parameterInPath() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter() { @Override public Map execute(final String defaultURI, final Map request, final TestingFrameworkRequestHandler requestHandler, final Map responseExpectations) throws TestingFrameworkException { @SuppressWarnings("unchecked") final Map query = (Map) request.get(QUERY); Assertions.assertTrue(query.containsKey("stuffParm"), "Parameters appended to the path should have been put in the query."); Assertions.assertTrue(query.containsKey("stuffParm2")); Assertions.assertEquals("stuff", query.get("stuffParm")); Assertions.assertEquals("stuff2", query.get("stuffParm2")); Assertions.assertEquals("/stuff", request.get(PATH)); return alreadyCheckedResponse(); } }; final TestingFramework framework = newFrameworkAndSetAdapter(adapter); final Map test = new HashMap<>(); // Add request. final Map request = new HashMap<>(); test.put(REQUEST, request); request.put(PATH, "/stuff?stuffParm=stuff&stuffParm2=stuff2"); framework.addTest(test); framework.runTests(); } }././@LongLink0100644 0000000 0000000 00000000156 00000250600 011613 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestTestingFrameworkRequestHandler.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestTestingFrameworkRequestHan0100644 0000000 0000000 00000005561 00000250600 033020 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.framework; import java.io.IOException; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.protocol.HttpContext; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class TestTestingFrameworkRequestHandler { @Test void assertNothingThrown() throws Exception { final TestingFrameworkRequestHandler handler = new TestingFrameworkRequestHandler() { @Override public void handle(final ClassicHttpRequest request, final ClassicHttpResponse response, final HttpContext context) throws HttpException, IOException { } }; handler.assertNothingThrown(); } @Test void assertNothingThrownThrows() throws Exception { final String errorMessage = "thrown intentionally"; final TestingFrameworkRequestHandler handler = new TestingFrameworkRequestHandler() { @Override public void handle(final ClassicHttpRequest request, final ClassicHttpResponse response, final HttpContext context) throws HttpException, IOException { thrown = new TestingFrameworkException(errorMessage); } }; handler.handle(null, null, null); final TestingFrameworkException exception = Assertions.assertThrows(TestingFrameworkException.class, () -> handler.assertNothingThrown()); Assertions.assertEquals(errorMessage, exception.getMessage(), "Unexpected message"); // a second call should not throw handler.assertNothingThrown(); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/0040755 0000000 0000000 00000000000 00000250600 023526 5ustar000000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/AlpnTest.java0100644 0000000 0000000 00000033411 00000250600 026122 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import static org.hamcrest.MatcherAssert.assertThat; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.concurrent.Future; import java.util.function.Function; import javax.net.ssl.SSLContext; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy; import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.extension.SecurityProviderResource; import org.apache.hc.core5.testing.extension.nio.H2AsyncRequesterResource; import org.apache.hc.core5.testing.extension.nio.H2AsyncServerResource; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; abstract class AlpnTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @RegisterExtension @Order(1) private final SecurityProviderResource securityProviderResource; private final Function serverTlsStrategyFactory; private final Function clientTlsStrategyFactory; @RegisterExtension @Order(2) private final H2AsyncServerResource serverResource; @RegisterExtension @Order(3) private final H2AsyncRequesterResource clientResource; public AlpnTest(final String securityProviderName, final Function serverTlsStrategyFactory, final Function clientTlsStrategyFactory) { this.securityProviderResource = new SecurityProviderResource(securityProviderName); this.serverTlsStrategyFactory = serverTlsStrategyFactory; this.clientTlsStrategyFactory = clientTlsStrategyFactory; this.serverResource = new H2AsyncServerResource(); this.serverResource.configure(bootstrap -> bootstrap .setIOReactorConfig( IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setRequestRouter(RequestRouter.>builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()) ); this.clientResource = new H2AsyncRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) ); } @BeforeEach void setup() throws Exception { final SSLContext serverSSLContext = SSLTestContexts.createServerSSLContext(securityProviderResource.securityProvider(), "TLS"); serverResource.configure(bootstrap -> bootstrap .setVersionPolicy(HttpVersionPolicy.NEGOTIATE) .setTlsStrategy(serverTlsStrategyFactory != null ? serverTlsStrategyFactory.apply(serverSSLContext) : new H2ServerTlsStrategy(serverSSLContext)) ); final SSLContext clientSSLContext = SSLTestContexts.createClientSSLContext(securityProviderResource.securityProvider(), "TLS"); clientResource.configure(bootstrap -> bootstrap .setVersionPolicy(HttpVersionPolicy.NEGOTIATE) .setTlsStrategy(clientTlsStrategyFactory != null ? clientTlsStrategyFactory.apply(clientSSLContext) : new H2ClientTlsStrategy(clientSSLContext)) ); } @Test void testForceHttp1() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), URIScheme.HTTPS); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientResource.start(); final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort()); final Future connectFuture = requester.connect(target, TIMEOUT, HttpVersionPolicy.FORCE_HTTP_1, null); final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); final Future> resultFuture1 = endpoint.execute( new BasicRequestProducer(Method.POST, target, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message1, CoreMatchers.notNullValue()); final HttpResponse response1 = message1.getHead(); assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); assertThat(response1.getVersion(), CoreMatchers.equalTo(HttpVersion.HTTP_1_1)); } @Test void testForceHttp1GlobalPolicy() throws Exception { serverResource.configure(bootstrap -> bootstrap.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1)); final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), URIScheme.HTTPS); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientResource.start(); final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort()); final Future connectFuture = requester.connect(target, TIMEOUT); final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); final Future> resultFuture1 = endpoint.execute( new BasicRequestProducer(Method.POST, target, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message1, CoreMatchers.notNullValue()); final HttpResponse response1 = message1.getHead(); assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); assertThat(response1.getVersion(), CoreMatchers.equalTo(HttpVersion.HTTP_1_1)); } @Test void testForceHttp2() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), URIScheme.HTTPS); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientResource.start(); final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort()); final Future connectFuture = requester.connect(target, TIMEOUT, HttpVersionPolicy.FORCE_HTTP_2, null); final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); final Future> resultFuture1 = endpoint.execute( new BasicRequestProducer(Method.POST, target, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message1, CoreMatchers.notNullValue()); final HttpResponse response1 = message1.getHead(); assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); assertThat(response1.getVersion(), CoreMatchers.equalTo(HttpVersion.HTTP_2)); } @Test void testForceHttp2GlobalPolicy() throws Exception { serverResource.configure(bootstrap -> bootstrap.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2)); final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), URIScheme.HTTPS); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientResource.start(); final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort()); final Future connectFuture = requester.connect(target, TIMEOUT); final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); final Future> resultFuture1 = endpoint.execute( new BasicRequestProducer(Method.POST, target, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message1, CoreMatchers.notNullValue()); final HttpResponse response1 = message1.getHead(); assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); assertThat(response1.getVersion(), CoreMatchers.equalTo(HttpVersion.HTTP_2)); } @Test void testNegotiateProtocol() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), URIScheme.HTTPS); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientResource.start(); final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort()); final Future connectFuture = requester.connect(target, TIMEOUT, HttpVersionPolicy.NEGOTIATE, null); final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); final Future> resultFuture1 = endpoint.execute( new BasicRequestProducer(Method.POST, target, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message1, CoreMatchers.notNullValue()); final HttpResponse response1 = message1.getHead(); assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); assertThat(response1.getVersion(), CoreMatchers.equalTo(HttpVersion.HTTP_2)); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/AlpnTests.java0100644 0000000 0000000 00000005246 00000250600 026312 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import org.apache.hc.core5.http2.ssl.ConscryptClientTlsStrategy; import org.apache.hc.core5.http2.ssl.ConscryptServerTlsStrategy; import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy; import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.condition.OS; class AlpnTests { @Nested @DisplayName("ALPN Oracle") class OracleJSSEAlpnTest extends AlpnTest { public OracleJSSEAlpnTest() throws Exception { super("Oracle", H2ServerTlsStrategy::new, H2ClientTlsStrategy::new); } } @Nested @DisplayName("ALPN Conscrypt (Java 9 or newer)") @DisabledOnOs(OS.MAC) @EnabledForJreRange(min = JRE.JAVA_9) class ConscryptJSSEAlpnTest extends AlpnTest { public ConscryptJSSEAlpnTest() throws Exception { super("Conscrypt", H2ServerTlsStrategy::new, H2ClientTlsStrategy::new); } } @Nested @DisplayName("ALPN Conscrypt (Conscrypt specific TLS strategies)") @DisabledOnOs(OS.MAC) class ConscryptJSSEAndStrategiesAlpnTest extends AlpnTest { public ConscryptJSSEAndStrategiesAlpnTest() throws Exception { super("Conscrypt", ConscryptServerTlsStrategy::new, ConscryptClientTlsStrategy::new); } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/AsyncServerBootstrapFilterTest.java0100644 0000000 0000000 00000016313 00000250600 032542 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import static org.hamcrest.MatcherAssert.assertThat; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.concurrent.Future; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.AsyncFilterChain; import org.apache.hc.core5.http.nio.AsyncPushProducer; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy; import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.extension.nio.HttpAsyncRequesterResource; import org.apache.hc.core5.testing.extension.nio.HttpAsyncServerResource; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; abstract class AsyncServerBootstrapFilterTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @RegisterExtension private final HttpAsyncServerResource serverResource; @RegisterExtension private final HttpAsyncRequesterResource clientResource; public AsyncServerBootstrapFilterTest() { this.serverResource = new HttpAsyncServerResource(); this.serverResource.configure(bootstrap -> bootstrap .setTlsStrategy(new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext())) .setIOReactorConfig( IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setRequestRouter(RequestRouter.>builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()) .addFilterLast("test-filter", (request, entityDetails, context, responseTrigger, chain) -> chain.proceed(request, entityDetails, context, new AsyncFilterChain.ResponseTrigger() { @Override public void sendInformation( final HttpResponse response) throws HttpException, IOException { responseTrigger.sendInformation(response); } @Override public void submitResponse( final HttpResponse response, final AsyncEntityProducer entityProducer) throws HttpException, IOException { response.setHeader("X-Test-Filter", "active"); responseTrigger.submitResponse(response, entityProducer); } @Override public void pushPromise( final HttpRequest promise, final AsyncPushProducer responseProducer) throws HttpException, IOException { responseTrigger.pushPromise(promise, responseProducer); } }))); this.clientResource = new HttpAsyncRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext())) .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build())); } @Test void testFilters() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), URIScheme.HTTP); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientResource.start(); final HttpHost target = new HttpHost("http", "localhost", address.getPort()); final Future> resultFuture = requester.execute( new BasicRequestProducer(Method.POST, target, "/filters", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message = resultFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message, CoreMatchers.notNullValue()); final HttpResponse response = message.getHead(); assertThat(response.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final Header testFilterHeader = response.getHeader("X-Test-Filter"); assertThat(testFilterHeader, CoreMatchers.notNullValue()); } } ././@LongLink0100644 0000000 0000000 00000000146 00000250600 011612 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/ClassicToAsyncHttp1TransportTest.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/ClassicToAsyncHttp1TransportTest.jav0100644 0000000 0000000 00000010775 00000250600 032617 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.Random; import java.util.concurrent.Future; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.io.entity.ByteArrayEntity; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; import org.apache.hc.core5.http.nio.support.classic.ClassicToAsyncRequestProducer; import org.apache.hc.core5.http.nio.support.classic.ClassicToAsyncResponseConsumer; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; abstract class ClassicToAsyncHttp1TransportTest extends ClassicToAsyncTransportTest { public ClassicToAsyncHttp1TransportTest(final URIScheme scheme, final HttpVersion version) { super(scheme, version); } @ValueSource(ints = {0, 2048, 10240}) @ParameterizedTest(name = "{displayName}; content length: {0}") void test_request_handling_no_keep_alive(final int contentSize) throws Exception { final HttpAsyncServer server = serverResource.start(); registerHandler("/echo", () -> new EchoHandler(1024)); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), scheme); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientResource.start(); final HttpHost target = new HttpHost(scheme.id, "localhost", address.getPort()); final int n = 10; for (int i = 0; i < n; i++) { final byte[] temp = new byte[contentSize]; new Random(System.currentTimeMillis()).nextBytes(temp); final ClassicHttpRequest request = ClassicRequestBuilder.post() .setHttpHost(target) .setPath("/echo/") .addHeader(HttpHeaders.CONNECTION, "Close") .setEntity(new ByteArrayEntity(temp, ContentType.DEFAULT_BINARY)) .build(); final ClassicToAsyncRequestProducer requestProducer = new ClassicToAsyncRequestProducer(request, TIMEOUT); final ClassicToAsyncResponseConsumer responseConsumer = new ClassicToAsyncResponseConsumer(TIMEOUT); requester.execute(requestProducer, responseConsumer, TIMEOUT, null); requestProducer.blockWaiting().execute(); try (ClassicHttpResponse response = responseConsumer.blockWaiting()) { Assertions.assertEquals(200, response.getCode()); final byte[] bytes = EntityUtils.toByteArray(response.getEntity()); Assertions.assertNotNull(bytes); Assertions.assertArrayEquals(temp, bytes); } } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/ClassicToAsyncIntegrationTests.java0100644 0000000 0000000 00000004753 00000250600 032510 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.URIScheme; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; class ClassicToAsyncIntegrationTests { @Nested @DisplayName("Classic over async transport (HTTP/1.1)") class ClassicOverAsyncTransport extends ClassicToAsyncHttp1TransportTest { public ClassicOverAsyncTransport() { super(URIScheme.HTTP, HttpVersion.HTTP_1_1); } } @Nested @DisplayName("Classic over async transport (HTTP/1.1, TLS)") class ClassicOverAsyncTransportTls extends ClassicToAsyncHttp1TransportTest { public ClassicOverAsyncTransportTls() { super(URIScheme.HTTPS, HttpVersion.HTTP_1_1); } } @Nested @DisplayName("Classic over async transport (HTTP/2)") class ClassicOverAsyncTransportH2 extends ClassicToAsyncTransportTest { public ClassicOverAsyncTransportH2() { super(URIScheme.HTTP, HttpVersion.HTTP_2); } } @Nested @DisplayName("Classic over async transport (HTTP/2, TLS)") class ClassicOverAsyncTransportH2Tls extends ClassicToAsyncTransportTest { public ClassicOverAsyncTransportH2Tls() { super(URIScheme.HTTPS, HttpVersion.HTTP_2); } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/ClassicToAsyncTransportTest.java0100644 0000000 0000000 00000044773 00000250600 032044 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Queue; import java.util.StringTokenizer; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.RequestNotExecutedException; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.entity.EntityTemplate; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http.nio.support.classic.ClassicToAsyncRequestProducer; import org.apache.hc.core5.http.nio.support.classic.ClassicToAsyncResponseConsumer; import org.apache.hc.core5.http.nio.support.classic.ClassicToAsyncServerExchangeHandler; import org.apache.hc.core5.http.support.BasicRequestBuilder; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy; import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.testing.Result; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.extension.classic.ExecutorResource; import org.apache.hc.core5.testing.extension.nio.H2AsyncRequesterResource; import org.apache.hc.core5.testing.extension.nio.H2AsyncServerResource; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; abstract class ClassicToAsyncTransportTest { static final Timeout TIMEOUT = Timeout.ofMinutes(1); final URIScheme scheme; @RegisterExtension final H2AsyncServerResource serverResource; @RegisterExtension final H2AsyncRequesterResource clientResource; @RegisterExtension final ExecutorResource executorResource; public ClassicToAsyncTransportTest(final URIScheme scheme, final HttpVersion version) { this.scheme = scheme; this.serverResource = new H2AsyncServerResource(); this.serverResource.configure(bootstrap -> bootstrap .setVersionPolicy(version.lessEquals(HttpVersion.HTTP_1_1) ? HttpVersionPolicy.FORCE_HTTP_1 : HttpVersionPolicy.FORCE_HTTP_2) .setTlsStrategy(new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext())) .setIOReactorConfig( IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setRequestRouter(RequestRouter.>builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()) ); this.clientResource = new H2AsyncRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setVersionPolicy(version.lessEquals(HttpVersion.HTTP_1_1) ? HttpVersionPolicy.FORCE_HTTP_1 : HttpVersionPolicy.FORCE_HTTP_2) .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext())) .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) ); this.executorResource = new ExecutorResource(5); } void registerHandler( final String pathPattern, final Supplier requestHandlerSupplier) { serverResource.configure(bootstrap -> bootstrap .setRequestRouter(RequestRouter.>builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, pathPattern, requestHandlerSupplier) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()) ); } void registerHandler(final String pathPattern, final HttpRequestHandler requestHandler) { registerHandler(pathPattern, () -> new ClassicToAsyncServerExchangeHandler( executorResource.getExecutorService(), requestHandler, LoggingExceptionCallback.INSTANCE)); } @Test void test_request_execution() throws Exception { final HttpAsyncServer server = serverResource.start(); registerHandler("/echo", () -> new EchoHandler(1024)); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), scheme); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientResource.start(); final HttpHost target = new HttpHost(scheme.id, "localhost", address.getPort()); final int n = 10; for (int i = 0; i < n; i++) { final ClassicHttpRequest request1 = ClassicRequestBuilder.post() .setHttpHost(target) .setPath("/echo") .setEntity(new EntityTemplate( -1, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8), null, outputStream -> { try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8))) { for (int ii = 0; ii < 500; ii++) { writer.write("0123456789abcdef"); writer.newLine(); } } })) .build(); final ClassicToAsyncRequestProducer requestProducer = new ClassicToAsyncRequestProducer(request1, 16, TIMEOUT); final ClassicToAsyncResponseConsumer responseConsumer = new ClassicToAsyncResponseConsumer(16, TIMEOUT); requester.execute(requestProducer, responseConsumer, TIMEOUT, null); requestProducer.blockWaiting().execute(); try (ClassicHttpResponse response = responseConsumer.blockWaiting()) { final HttpEntity entity = response.getEntity(); final ContentType contentType = ContentType.parse(entity.getContentType()); final Charset charset = ContentType.getCharset(contentType, StandardCharsets.UTF_8); try (final InputStream inputStream = entity.getContent()) { final StringBuilder buffer = new StringBuilder(); final byte[] tmp = new byte[16]; int l; while ((l = inputStream.read(tmp)) != -1) { buffer.append(charset.decode(ByteBuffer.wrap(tmp, 0, l))); } final StringTokenizer t1 = new StringTokenizer(buffer.toString(), "\r\n"); while (t1.hasMoreTokens()) { Assertions.assertEquals("0123456789abcdef", t1.nextToken()); } } } } } @ParameterizedTest(name = "method {0}") @ValueSource(strings = {"GET", "POST", "HEAD"}) void test_request_handling(final String method) throws Exception { registerHandler("/hello", (request, response, context) -> { final HttpEntity requestEntity = request.getEntity(); if (requestEntity != null) { EntityUtils.consume(requestEntity); } final ContentType contentType = ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8); final Charset charset = contentType.getCharset(); final HttpEntity responseEntity = new EntityTemplate( contentType, outputStream -> { try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, charset))) { for (int i = 0; i < 500; i++) { writer.write("0123456789abcdef\r\n"); } } }); response.setEntity(responseEntity); }); final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), scheme); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientResource.start(); final HttpHost target = new HttpHost(scheme.id, "localhost", address.getPort()); final int n = 10; final CountDownLatch countDownLatch = new CountDownLatch(n); final Queue> resultQueue = new ConcurrentLinkedQueue<>(); for (int i = 0; i < n; i++) { final BasicHttpRequest request1 = BasicRequestBuilder.create(method) .setHttpHost(target) .setPath("/hello") .build(); final AsyncEntityProducer entityProducer = Method.POST.isSame(method) ? new MultiLineEntityProducer("xxxxxxxxxxxx", 250) : null; requester.execute( new BasicRequestProducer(request1, entityProducer), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, new FutureCallback>() { @Override public void completed(final Message responseMessage) { resultQueue.add(new Result<>( request1, responseMessage.getHead(), responseMessage.getBody())); countDownLatch.countDown(); } @Override public void failed(final Exception ex) { resultQueue.add(new Result<>(request1, ex)); countDownLatch.countDown(); } @Override public void cancelled() { failed(new RequestNotExecutedException()); } }); } Assertions.assertTrue(countDownLatch.await(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()), "Request executions have not completed in time"); for (final Result result : resultQueue) { if (result.isOK()) { Assertions.assertNotNull(result.response); Assertions.assertEquals(HttpStatus.SC_OK, result.response.getCode(), "Response message returned non 200 status"); if (Method.HEAD.isSame(method)) { Assertions.assertNull(result.content); } else { Assertions.assertNotNull(result.content); final StringTokenizer t1 = new StringTokenizer(result.content, "\r\n"); while (t1.hasMoreTokens()) { Assertions.assertEquals("0123456789abcdef", t1.nextToken()); } } } else { Assertions.fail(result.exception); } } } @Test void test_request_handling_full_streaming() throws Exception { registerHandler("/echo", (request, response, context) -> { final HttpEntity requestEntity = request.getEntity(); final ContentType contentType = requestEntity != null ? ContentType.parseLenient(requestEntity.getContentType()) : ContentType.TEXT_PLAIN; final Charset charset = contentType.getCharset(StandardCharsets.UTF_8); final HttpEntity responseEntity; if (requestEntity != null) { responseEntity = new EntityTemplate( contentType, outputStream -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(requestEntity.getContent())); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, charset))) { String line; while ((line = reader.readLine()) != null) { writer.write(line); writer.newLine(); } } }); } else { responseEntity = null; } response.setEntity(responseEntity); }); final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), scheme); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientResource.start(); final HttpHost target = new HttpHost(scheme.id, "localhost", address.getPort()); final int n = 10; final CountDownLatch countDownLatch = new CountDownLatch(n); final Queue> resultQueue = new ConcurrentLinkedQueue<>(); for (int i = 0; i < n; i++) { final BasicHttpRequest request1 = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/echo") .build(); final AsyncEntityProducer entityProducer = new MultiLineEntityProducer("0123456789abcdef", 500); requester.execute( new BasicRequestProducer(request1, entityProducer), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, new FutureCallback>() { @Override public void completed(final Message responseMessage) { resultQueue.add(new Result<>( request1, responseMessage.getHead(), responseMessage.getBody())); countDownLatch.countDown(); } @Override public void failed(final Exception ex) { resultQueue.add(new Result<>(request1, ex)); countDownLatch.countDown(); } @Override public void cancelled() { failed(new RequestNotExecutedException()); } }); } Assertions.assertTrue(countDownLatch.await(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()), "Request executions have not completed in time"); for (final Result result : resultQueue) { if (result.isOK()) { Assertions.assertNotNull(result.response); Assertions.assertEquals(HttpStatus.SC_OK, result.response.getCode(), "Response message returned non 200 status"); Assertions.assertNotNull(result.content); final StringTokenizer t1 = new StringTokenizer(result.content, "\r\n"); while (t1.hasMoreTokens()) { Assertions.assertEquals("0123456789abcdef", t1.nextToken()); } } else { Assertions.fail(result.exception); } } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/EchoHandler.java0100644 0000000 0000000 00000013402 00000250600 026542 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.protocol.HttpContext; public class EchoHandler implements AsyncServerExchangeHandler { private final static int MIN_CHUNK = 4096; private final int initBufferSize; private final AtomicInteger capacityIncrement; private volatile ByteBuffer buffer; private volatile CapacityChannel inputCapacityChannel; private volatile DataStreamChannel outputDataChannel; private volatile boolean endStream; public EchoHandler(final int bufferSize) { this.initBufferSize = bufferSize; this.capacityIncrement = new AtomicInteger(); this.buffer = ByteBuffer.allocate(bufferSize); } private void ensureCapacity(final int chunk) { if (buffer.remaining() < chunk) { final ByteBuffer oldBuffer = buffer; oldBuffer.flip(); final int newCapacity = oldBuffer.remaining() + Math.max(chunk, MIN_CHUNK); buffer = ByteBuffer.allocate(newCapacity); buffer.put(oldBuffer); } } private void signalCapacity() throws IOException { if (inputCapacityChannel != null) { if (capacityIncrement.get() > MIN_CHUNK) { final int n = capacityIncrement.getAndSet(0); if (n > 0) { inputCapacityChannel.update(n); } } } } @Override public void handleRequest( final HttpRequest request, final EntityDetails entityDetails, final ResponseChannel responseChannel, final HttpContext context) throws HttpException, IOException { final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK); responseChannel.sendResponse(response, entityDetails, context); } @Override public void consume(final ByteBuffer src) throws IOException { if (buffer.position() == 0) { if (outputDataChannel != null) { final int bytesWritten = outputDataChannel.write(src); if (bytesWritten > 0) { capacityIncrement.addAndGet(bytesWritten); } } } if (src.hasRemaining()) { ensureCapacity(src.remaining()); buffer.put(src); if (outputDataChannel != null) { outputDataChannel.requestOutput(); } } signalCapacity(); } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { if (buffer.hasRemaining()) { final int n = Math.min(initBufferSize, buffer.remaining()) + capacityIncrement.getAndSet(0); capacityChannel.update(n); } inputCapacityChannel = capacityChannel; } @Override public void streamEnd(final List trailers) throws HttpException, IOException { endStream = true; inputCapacityChannel = null; if (buffer.position() == 0) { if (outputDataChannel != null) { outputDataChannel.endStream(); } } else { if (outputDataChannel != null) { outputDataChannel.requestOutput(); } } } @Override public int available() { return buffer.position(); } @Override public void produce(final DataStreamChannel channel) throws IOException { outputDataChannel = channel; buffer.flip(); if (buffer.hasRemaining()) { final int bytesWritten = channel.write(buffer); if (bytesWritten > 0) { capacityIncrement.addAndGet(bytesWritten); } } buffer.compact(); if (buffer.position() == 0 && endStream) { channel.endStream(); } signalCapacity(); } @Override public void failed(final Exception cause) { } @Override public void releaseResources() { } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2AlpnTest.java0100644 0000000 0000000 00000015750 00000250600 026322 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import static org.hamcrest.MatcherAssert.assertThat; 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 java.net.InetAddress; import java.net.InetSocketAddress; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer; import org.apache.hc.core5.http.nio.ssl.BasicServerTlsStrategy; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http2.impl.nio.ProtocolNegotiationException; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2MultiplexingRequester; import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy; import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.extension.nio.H2AsyncServerResource; import org.apache.hc.core5.testing.extension.nio.H2MultiplexingRequesterResource; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; abstract class H2AlpnTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); private final boolean strictALPN; private final boolean h2Allowed; @RegisterExtension private final H2AsyncServerResource serverResource; @RegisterExtension private final H2MultiplexingRequesterResource clientResource; public H2AlpnTest(final boolean strictALPN, final boolean h2Allowed) throws Exception { this.strictALPN = strictALPN; this.h2Allowed = h2Allowed; final TlsStrategy serverTlsStrategy = h2Allowed ? new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext()) : new BasicServerTlsStrategy(SSLTestContexts.createServerSSLContext()); this.serverResource = new H2AsyncServerResource(); this.serverResource.configure(bootstrap -> bootstrap .setIOReactorConfig( IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setTlsStrategy(serverTlsStrategy) .setRequestRouter(RequestRouter.>builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()) ); final TlsStrategy clientTlsStrategy = new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext()); this.clientResource = new H2MultiplexingRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setTlsStrategy(clientTlsStrategy) .setStrictALPNHandshake(strictALPN) ); } @Test void testALPN() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), URIScheme.HTTPS); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final H2MultiplexingRequester requester = clientResource.start(); final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort()); final Future> resultFuture1 = requester.execute( new BasicRequestProducer(Method.POST, target, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message1; try { message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); } catch (final ExecutionException e) { final Throwable cause = e.getCause(); assertFalse(h2Allowed, "h2 negotiation was enabled, but h2 was not negotiated"); assertTrue(cause instanceof ProtocolNegotiationException); assertEquals("ALPN: missing application protocol", cause.getMessage()); assertTrue(strictALPN, "strict ALPN mode was not enabled, but the client negotiator still threw"); return; } assertTrue(h2Allowed, "h2 negotiation was disabled, but h2 was negotiated"); assertThat(message1, CoreMatchers.notNullValue()); final HttpResponse response1 = message1.getHead(); assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body1 = message1.getBody(); assertThat(body1, CoreMatchers.equalTo("some stuff")); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2AlpnTests.java0100644 0000000 0000000 00000003740 00000250600 026501 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; class H2AlpnTests { @Nested @DisplayName("Strict h2 ALPN, h2 allowed") class StrictH2AlpnH2Allowed extends H2AlpnTest { public StrictH2AlpnH2Allowed() throws Exception { super(true, true); } } @Nested @DisplayName("Strict h2 ALPN, h2 disallowed") class StrictH2AlpnH2Disallowed extends H2AlpnTest { public StrictH2AlpnH2Disallowed() throws Exception { super(true, false); } } @Nested @DisplayName("Non-strict h2 ALPN, h2 allowed") class NonStrictH2AlpnH2Disallowed extends H2AlpnTest { public NonStrictH2AlpnH2Disallowed() throws Exception { super(false, true); } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ConnPoolTest.java0100644 0000000 0000000 00000015314 00000250600 027153 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicLong; import org.apache.hc.core5.concurrent.CountDownLatchFutureCallback; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2MultiplexingRequester; import org.apache.hc.core5.http2.nio.command.PingCommand; import org.apache.hc.core5.http2.nio.pool.H2ConnPool; import org.apache.hc.core5.http2.nio.support.BasicPingHandler; import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy; import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy; import org.apache.hc.core5.reactor.Command; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.extension.nio.H2AsyncServerResource; import org.apache.hc.core5.testing.extension.nio.H2MultiplexingRequesterResource; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; class H2ConnPoolTest { private static final Timeout TIMEOUT = Timeout.ofSeconds(30); private final AtomicLong clientConnCount; @RegisterExtension private final H2AsyncServerResource serverResource; @RegisterExtension private final H2MultiplexingRequesterResource clientResource; public H2ConnPoolTest() throws Exception { this.serverResource = new H2AsyncServerResource(); this.serverResource.configure(bootstrap -> bootstrap .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2) .setTlsStrategy(new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext())) .setIOReactorConfig( IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setRequestRouter(RequestRouter.>builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()) ); this.clientConnCount = new AtomicLong(); this.clientResource = new H2MultiplexingRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext())) .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setIOSessionListener(new LoggingIOSessionListener() { @Override public void connected(final IOSession session) { clientConnCount.incrementAndGet(); super.connected(session); } }) ); } @BeforeEach void resetCounts() { clientConnCount.set(0); } @Test void testManyGetSession() throws Exception { final int n = 200; final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), URIScheme.HTTP); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpHost target = new HttpHost(URIScheme.HTTP.id, "localhost", address.getPort()); final H2MultiplexingRequester requester = clientResource.start(); final H2ConnPool connPool = requester.getConnPool(); final CountDownLatchFutureCallback latch = new CountDownLatchFutureCallback(n) { @Override public void completed(final IOSession session) { session.enqueue(new PingCommand(new BasicPingHandler( result -> countDown() )), Command.Priority.IMMEDIATE); } }; for (int i = 0; i < n; i++) { connPool.getSession(target, TIMEOUT, latch); } Assertions.assertTrue(latch.await(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); requester.initiateShutdown(); requester.awaitShutdown(TIMEOUT); Assertions.assertEquals(1, clientConnCount.get()); } @Test void testManyGetSessionFailures() throws Exception { final int n = 200; final HttpHost target = new HttpHost(URIScheme.HTTP.id, "pampa.invalid", 8888); final H2MultiplexingRequester requester = clientResource.start(); final H2ConnPool connPool = requester.getConnPool(); final CountDownLatchFutureCallback latch = new CountDownLatchFutureCallback<>(n); for (int i = 0; i < n; i++) { connPool.getSession(target, TIMEOUT, latch); } requester.initiateShutdown(); requester.awaitShutdown(TIMEOUT); Assertions.assertEquals(0, clientConnCount.get()); } } ././@LongLink0100644 0000000 0000000 00000000145 00000250600 011611 Lustar 0000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2CoreTransportMultiplexingTest.javahttpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2CoreTransportMultiplexingTest.java0100644 0000000 0000000 00000037375 00000250600 032646 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import static org.hamcrest.MatcherAssert.assertThat; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.LinkedList; import java.util.Queue; import java.util.Random; import java.util.concurrent.Future; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.apache.hc.core5.concurrent.Cancellable; import org.apache.hc.core5.concurrent.CountDownLatchFutureCallback; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer; import org.apache.hc.core5.http.nio.support.BasicClientExchangeHandler; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2MultiplexingRequester; import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy; import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.extension.nio.H2AsyncServerResource; import org.apache.hc.core5.testing.extension.nio.H2MultiplexingRequesterResource; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; abstract class H2CoreTransportMultiplexingTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); private final URIScheme scheme; @RegisterExtension private final H2AsyncServerResource serverResource; @RegisterExtension private final H2MultiplexingRequesterResource clientResource; public H2CoreTransportMultiplexingTest(final URIScheme scheme) { this.scheme = scheme; this.serverResource = new H2AsyncServerResource(); this.serverResource.configure(bootstrap -> bootstrap .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2) .setTlsStrategy(new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext())) .setIOReactorConfig( IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setRequestRouter(RequestRouter.>builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()) ); this.clientResource = new H2MultiplexingRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext())) .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) ); } @Test void testSequentialRequests() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), scheme); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final H2MultiplexingRequester requester = clientResource.start(); final HttpHost target = new HttpHost(scheme.id, "localhost", address.getPort()); final Future> resultFuture1 = requester.execute( new BasicRequestProducer(Method.POST, target, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message1, CoreMatchers.notNullValue()); final HttpResponse response1 = message1.getHead(); assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body1 = message1.getBody(); assertThat(body1, CoreMatchers.equalTo("some stuff")); final Future> resultFuture2 = requester.execute( new BasicRequestProducer(Method.POST, target, "/other-stuff", new StringAsyncEntityProducer("some other stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message2 = resultFuture2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message2, CoreMatchers.notNullValue()); final HttpResponse response2 = message2.getHead(); assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body2 = message2.getBody(); assertThat(body2, CoreMatchers.equalTo("some other stuff")); final Future> resultFuture3 = requester.execute( new BasicRequestProducer(Method.POST, target, "/more-stuff", new StringAsyncEntityProducer("some more stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message3 = resultFuture3.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message3, CoreMatchers.notNullValue()); final HttpResponse response3 = message3.getHead(); assertThat(response3.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body3 = message3.getBody(); assertThat(body3, CoreMatchers.equalTo("some more stuff")); } @Test void testLargeRequest() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), scheme); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final H2MultiplexingRequester requester = clientResource.start(); final HttpHost target = new HttpHost(scheme.id, "localhost", address.getPort()); final String content = IntStream.range(0, 1000).mapToObj(i -> "a lot of stuff").collect(Collectors.joining(" ")); final Future> resultFuture = requester.execute( new BasicRequestProducer(Method.POST, target, "/a-lot-of-stuff", AsyncEntityProducers.create(content, ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message = resultFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message, CoreMatchers.notNullValue()); final HttpResponse response = message.getHead(); assertThat(response.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body = message.getBody(); assertThat(body, CoreMatchers.equalTo(content)); } @Test void testMultiplexedRequests() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), scheme); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final H2MultiplexingRequester requester = clientResource.start(); final HttpHost target = new HttpHost(scheme.id, "localhost", address.getPort()); final Queue>> queue = new LinkedList<>(); queue.add(requester.execute( new BasicRequestProducer(Method.POST, target, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null)); queue.add(requester.execute( new BasicRequestProducer(Method.POST, target, "/other-stuff", new StringAsyncEntityProducer("some other stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null)); queue.add(requester.execute( new BasicRequestProducer(Method.POST, target, "/more-stuff", new StringAsyncEntityProducer("some more stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null)); while (!queue.isEmpty()) { final Future> resultFuture = queue.remove(); final Message message = resultFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message, CoreMatchers.notNullValue()); final HttpResponse response = message.getHead(); assertThat(response.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body = message.getBody(); assertThat(body, CoreMatchers.containsString("stuff")); } } @Test void testValidityCheck() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), scheme); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final H2MultiplexingRequester requester = clientResource.start(); requester.setValidateAfterInactivity(TimeValue.ofMilliseconds(10)); final HttpHost target = new HttpHost(scheme.id, "localhost", address.getPort()); final Future> resultFuture1 = requester.execute( new BasicRequestProducer(Method.POST, target, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message1, CoreMatchers.notNullValue()); final HttpResponse response1 = message1.getHead(); assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body1 = message1.getBody(); assertThat(body1, CoreMatchers.equalTo("some stuff")); Thread.sleep(100); final Future> resultFuture2 = requester.execute( new BasicRequestProducer(Method.POST, target, "/other-stuff", new StringAsyncEntityProducer("some other stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message2 = resultFuture2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message2, CoreMatchers.notNullValue()); final HttpResponse response2 = message2.getHead(); assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body2 = message2.getBody(); assertThat(body2, CoreMatchers.equalTo("some other stuff")); Thread.sleep(100); final Future> resultFuture3 = requester.execute( new BasicRequestProducer(Method.POST, target, "/more-stuff", new StringAsyncEntityProducer("some more stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message3 = resultFuture3.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message3, CoreMatchers.notNullValue()); final HttpResponse response3 = message3.getHead(); assertThat(response3.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body3 = message3.getBody(); assertThat(body3, CoreMatchers.equalTo("some more stuff")); } @Test void testMultiplexedRequestCancellation() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), scheme); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final H2MultiplexingRequester requester = clientResource.start(); final int reqNo = 20; final CountDownLatchFutureCallback> countDownLatch = new CountDownLatchFutureCallback<>(reqNo); final Random random = new Random(); final HttpHost target = new HttpHost(scheme.id, "localhost", address.getPort()); for (int i = 0; i < reqNo; i++) { final Cancellable cancellable = requester.execute( new BasicClientExchangeHandler<>(new BasicRequestProducer(Method.POST, target, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), countDownLatch), TIMEOUT, HttpCoreContext.create()); Thread.sleep(random.nextInt(10)); cancellable.cancel(); } assertThat(countDownLatch.await(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()), CoreMatchers.equalTo(true)); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2CoreTransportTest.java0100644 0000000 0000000 00000007763 00000250600 030242 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy; import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.extension.nio.H2AsyncRequesterResource; import org.apache.hc.core5.testing.extension.nio.H2AsyncServerResource; import org.apache.hc.core5.util.Timeout; import org.junit.jupiter.api.extension.RegisterExtension; abstract class H2CoreTransportTest extends HttpCoreTransportTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @RegisterExtension private final H2AsyncServerResource serverResource; @RegisterExtension private final H2AsyncRequesterResource clientResource; public H2CoreTransportTest(final URIScheme scheme) { this(scheme, null); } public H2CoreTransportTest(final URIScheme scheme, final String tlsProtocol) { super(scheme); this.serverResource = new H2AsyncServerResource(); this.serverResource.configure(bootstrap -> bootstrap .setVersionPolicy(HttpVersionPolicy.NEGOTIATE) .setTlsStrategy(new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext(tlsProtocol))) .setIOReactorConfig( IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setRequestRouter(RequestRouter.>builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()) ); this.clientResource = new H2AsyncRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setVersionPolicy(HttpVersionPolicy.NEGOTIATE) .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext(tlsProtocol))) .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) ); } @Override HttpAsyncServer serverStart() throws IOException { return serverResource.start(); } @Override HttpAsyncRequester clientStart() { return clientResource.start(); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2IntegrationTest.java0100644 0000000 0000000 00000050231 00000250600 027704 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import static org.hamcrest.MatcherAssert.assertThat; import java.io.IOException; import java.net.InetSocketAddress; import java.util.LinkedList; import java.util.Queue; import java.util.StringTokenizer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EndpointDetails; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.nio.AsyncServerRequestHandler; import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers; import org.apache.hc.core5.http.nio.entity.DiscardingEntityConsumer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.support.AbstractAsyncPushHandler; import org.apache.hc.core5.http.nio.support.AsyncResponseBuilder; import org.apache.hc.core5.http.nio.support.BasicPushProducer; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http.nio.support.BasicResponseProducer; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.support.BasicRequestBuilder; import org.apache.hc.core5.http2.H2Error; import org.apache.hc.core5.http2.H2StreamResetException; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.nio.command.PingCommand; import org.apache.hc.core5.http2.nio.support.BasicPingHandler; import org.apache.hc.core5.reactor.Command; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.testing.extension.nio.H2TestResources; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; abstract class H2IntegrationTest extends HttpIntegrationTest { @RegisterExtension private final H2TestResources resources; public H2IntegrationTest(final URIScheme scheme) { super(scheme); this.resources = new H2TestResources(scheme, TIMEOUT); } @Override protected HttpTestServer server() { return resources.server(); } @Override protected HttpTestClient client() { return resources.client(); } @BeforeEach void setup() { resources.server().configure(H2Config.DEFAULT); resources.client().configure(H2Config.DEFAULT); } @Test @Override void testSlowResponseConsumer() throws Exception { final H2TestClient client = resources.client(); client.configure(H2Config.custom() .setInitialWindowSize(16) .build()); client.start(); super.testSlowResponseConsumer(); } @Test void testSlowResponseProducer() throws Exception { final H2TestClient client = resources.client(); client.configure(H2Config.custom() .setInitialWindowSize(512) .build()); client.start(); super.testSlowResponseProducer(); } @ParameterizedTest(name = "Max concurrent streams: {0}") @ValueSource(ints = {200, 20, 1}) void testPush(final int maxConcurrentStreams) throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); server.register("/hello", () -> new MessageExchangeHandler(new DiscardingEntityConsumer<>()) { @Override protected void handle( final Message request, final AsyncServerRequestHandler.ResponseTrigger responseTrigger, final HttpContext context) throws IOException, HttpException { responseTrigger.pushPromise( new BasicHttpRequest(Method.GET, new HttpHost(scheme.id, "localhost"), "/stuff"), context, new BasicPushProducer(new MultiLineEntityProducer("Pushing lots of stuff", 500))); responseTrigger.submitResponse( AsyncResponseBuilder.create(HttpStatus.SC_OK).setEntity("Hi there", ContentType.TEXT_PLAIN).build(), context); } }); server.configure(H2Config.custom() .setMaxConcurrentStreams(maxConcurrentStreams) .build()); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.configure(H2Config.custom() .setPushEnabled(true) .setMaxConcurrentStreams(maxConcurrentStreams) .build()); client.start(); final int reqNo = 200; final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final CountDownLatch latch = new CountDownLatch(reqNo); final Queue>> responseQueue = new LinkedList<>(); final Queue> pushMessageQueue = new LinkedList<>(); for (int i = 0; i < reqNo; i++) { final BasicHttpRequest request = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/hello") .build(); responseQueue.add(streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), (r, c) -> new AbstractAsyncPushHandler>(new BasicResponseConsumer<>(new StringAsyncEntityConsumer())) { @Override protected void handleResponse( final HttpRequest promise, final Message responseMessage) throws IOException, HttpException { pushMessageQueue.add(responseMessage); latch.countDown(); } @Override protected void handleError(final HttpRequest promise, final Exception cause) { latch.countDown(); } }, null, new FutureCallback>() { @Override public void completed(final Message result) { } @Override public void failed(final Exception ex) { latch.countDown(); } @Override public void cancelled() { latch.countDown(); } })); } Assertions.assertTrue(latch.await(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); Assertions.assertEquals(reqNo, responseQueue.size()); Assertions.assertEquals(reqNo, pushMessageQueue.size()); while (!responseQueue.isEmpty()) { final Future> future = responseQueue.remove(); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response1 = result.getHead(); final String entity = result.getBody(); Assertions.assertNotNull(response1); Assertions.assertEquals(200, response1.getCode()); Assertions.assertEquals("Hi there", entity); } while (!pushMessageQueue.isEmpty()) { final Message pushMessage = pushMessageQueue.remove(); Assertions.assertNotNull(pushMessage); final HttpResponse response = pushMessage.getHead(); final String entity = pushMessage.getBody(); Assertions.assertEquals(200, response.getCode()); Assertions.assertNotNull(entity); final StringTokenizer t1 = new StringTokenizer(entity, "\r\n"); while (t1.hasMoreTokens()) { Assertions.assertEquals("Pushing lots of stuff", t1.nextToken()); } } } @Test void testPushRefused() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); final CountDownLatch latch = new CountDownLatch(2); final Queue pushResultQueue = new LinkedList<>(); server.register("/hello", () -> new MessageExchangeHandler(new DiscardingEntityConsumer<>()) { @Override protected void handle( final Message request, final AsyncServerRequestHandler.ResponseTrigger responseTrigger, final HttpContext context) throws IOException, HttpException { responseTrigger.pushPromise( new BasicHttpRequest(Method.GET, new HttpHost(scheme.id, "localhost"), "/stuff"), context, new BasicPushProducer(new MultiLineEntityProducer("Pushing lots of stuff", 50000)) { @Override public void failed(final Exception cause) { pushResultQueue.add(cause); latch.countDown(); } }); responseTrigger.pushPromise( new BasicHttpRequest(Method.GET, new HttpHost(scheme.id, "localhost"), "/more-stuff"), context, new BasicPushProducer(new MultiLineEntityProducer("Pushing lots of stuff", 50000)) { @Override public void failed(final Exception cause) { pushResultQueue.add(cause); latch.countDown(); } }); responseTrigger.submitResponse( new BasicResponseProducer(HttpStatus.SC_OK, AsyncEntityProducers.create("Hi there")), context); } }); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.configure(H2Config.custom() .setPushEnabled(true) .build()); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final BasicHttpRequest request1 = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/hello") .build(); final Future> future1 = streamEndpoint.execute( new BasicRequestProducer(request1, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), (r, c) -> null, // refuse all push messages null, null); final Message result1 = future1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result1); final HttpResponse response1 = result1.getHead(); final String entity1 = result1.getBody(); Assertions.assertNotNull(response1); Assertions.assertEquals(200, response1.getCode()); Assertions.assertEquals("Hi there", entity1); Assertions.assertTrue(latch.await(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); Assertions.assertEquals(2, pushResultQueue.size()); final Object result2 = pushResultQueue.poll(); Assertions.assertNotNull(result2); Assertions.assertTrue(result2 instanceof H2StreamResetException); Assertions.assertEquals(H2Error.REFUSED_STREAM.getCode(), ((H2StreamResetException) result2).getCode()); final Object result3 = pushResultQueue.poll(); Assertions.assertNotNull(result3); Assertions.assertTrue(result3 instanceof H2StreamResetException); Assertions.assertEquals(H2Error.REFUSED_STREAM.getCode(), ((H2StreamResetException) result3).getCode()); } @ParameterizedTest(name = "Max concurrent streams: {0}") @ValueSource(ints = {200, 20, 1}) void testExcessOfConcurrentStreams(final int maxConcurrentStreams) throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); server.register("/", () -> new MultiLineResponseHandler("0123456789abcdef", 2000)); server.configure(H2Config.custom() .setMaxConcurrentStreams(maxConcurrentStreams) .build()); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.configure(H2Config.custom() .setMaxConcurrentStreams(maxConcurrentStreams ) .build()); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final Queue>> queue = new LinkedList<>(); for (int i = 0; i < 2000; i++) { final BasicHttpRequest request1 = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/") .build(); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request1, null), new BasicResponseConsumer<>(new DiscardingEntityConsumer<>()), null); queue.add(future); } while (!queue.isEmpty()) { final Future> future = queue.remove(); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response = result.getHead(); Assertions.assertNotNull(response); Assertions.assertEquals(200, response.getCode()); } } @Test void testConnectionPing() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final int n = 10; final CountDownLatch latch = new CountDownLatch(n); final AtomicInteger count = new AtomicInteger(0); for (int i = 0; i < n; i++) { final BasicHttpRequest request = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/hello") .build(); streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); streamEndpoint.execute(new PingCommand(new BasicPingHandler(result -> { if (result) { count.incrementAndGet(); } latch.countDown(); })), Command.Priority.NORMAL); } Assertions.assertTrue(latch.await(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); Assertions.assertEquals(n, count.get()); } @Test void testRequestWithInvalidConnectionHeader() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future sessionFuture = client.requestSession(new HttpHost("localhost", serverEndpoint.getPort()), TIMEOUT, null); final IOSession session = sessionFuture.get(); try (final ClientSessionEndpoint streamEndpoint = new ClientSessionEndpoint(session)) { final BasicHttpRequest request = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/hello") .build(); request.addHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); final HttpCoreContext context = HttpCoreContext.create(); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), context, null); final ExecutionException exception = Assertions.assertThrows(ExecutionException.class, () -> future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); assertThat(exception.getCause(), CoreMatchers.instanceOf(ProtocolException.class)); final EndpointDetails endpointDetails = context.getEndpointDetails(); assertThat(endpointDetails.getRequestCount(), CoreMatchers.equalTo(0L)); } } @ParameterizedTest @ValueSource(strings = {"GET", "POST"}) void testHeaderTooLarge(final String method) throws Exception { final H2TestServer server = resources.server(); server.configure(H2Config.custom() .setMaxHeaderListSize(100) .build()); super.testHeaderTooLarge(method); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerBootstrapFiltersTest.java0100644 0000000 0000000 00000016560 00000250600 032125 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import static org.hamcrest.MatcherAssert.assertThat; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.concurrent.Future; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.AsyncFilterChain; import org.apache.hc.core5.http.nio.AsyncPushProducer; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy; import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.extension.nio.H2AsyncRequesterResource; import org.apache.hc.core5.testing.extension.nio.H2AsyncServerResource; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; abstract class H2ServerBootstrapFiltersTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @RegisterExtension private final H2AsyncServerResource serverResource; @RegisterExtension private final H2AsyncRequesterResource clientResource; H2ServerBootstrapFiltersTest() { this.serverResource = new H2AsyncServerResource(); this.serverResource.configure(bootstrap -> bootstrap .setVersionPolicy(HttpVersionPolicy.NEGOTIATE) .setTlsStrategy(new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext())) .setIOReactorConfig( IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setRequestRouter(RequestRouter.>builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()) .addFilterLast("test-filter", (request, entityDetails, context, responseTrigger, chain) -> chain.proceed(request, entityDetails, context, new AsyncFilterChain.ResponseTrigger() { @Override public void sendInformation( final HttpResponse response) throws HttpException, IOException { responseTrigger.sendInformation(response); } @Override public void submitResponse( final HttpResponse response, final AsyncEntityProducer entityProducer) throws HttpException, IOException { response.setHeader("X-Test-Filter", "active"); responseTrigger.submitResponse(response, entityProducer); } @Override public void pushPromise( final HttpRequest promise, final AsyncPushProducer responseProducer) throws HttpException, IOException { responseTrigger.pushPromise(promise, responseProducer); } }))); this.clientResource = new H2AsyncRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setVersionPolicy(HttpVersionPolicy.NEGOTIATE) .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext())) .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build())); } @Test void testSequentialRequests() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), URIScheme.HTTP); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientResource.start(); final HttpHost target = new HttpHost("http", "localhost", address.getPort()); final Future> resultFuture = requester.execute( new BasicRequestProducer(Method.POST, target, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message = resultFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message, CoreMatchers.notNullValue()); final HttpResponse response = message.getHead(); assertThat(response.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final Header testFilterHeader = response.getHeader("X-Test-Filter"); assertThat(testFilterHeader, CoreMatchers.notNullValue()); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1AuthenticationTest.java0100644 0000000 0000000 00000032617 00000250600 031137 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import static org.hamcrest.MatcherAssert.assertThat; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.util.Random; import java.util.concurrent.Future; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.impl.bootstrap.StandardFilter; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.support.AbstractAsyncServerAuthFilter; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy; import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.extension.nio.HttpAsyncRequesterResource; import org.apache.hc.core5.testing.extension.nio.HttpAsyncServerResource; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; abstract class Http1AuthenticationTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @RegisterExtension private final HttpAsyncServerResource serverResource; @RegisterExtension private final HttpAsyncRequesterResource clientResource; public Http1AuthenticationTest(final boolean respondImmediately) { this.serverResource = new HttpAsyncServerResource(); this.serverResource.configure(bootstrap -> bootstrap .setTlsStrategy(new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext())) .setIOReactorConfig( IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setRequestRouter(RequestRouter.>builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()) .replaceFilter(StandardFilter.EXPECT_CONTINUE.name(), new AbstractAsyncServerAuthFilter(respondImmediately) { @Override protected String parseChallengeResponse( final String challenge, final HttpContext context) throws HttpException { return challenge; } @Override protected boolean authenticate( final String challengeResponse, final URIAuthority authority, final String requestUri, final HttpContext context) { return challengeResponse != null && challengeResponse.equals("let me pass"); } @Override protected String generateChallenge( final String challengeResponse, final URIAuthority authority, final String requestUri, final HttpContext context) { return "who goes there?"; } @Override protected AsyncEntityProducer generateResponseContent(final HttpResponse unauthorized) { return AsyncEntityProducers.create("You shall not pass!!!"); } }) ); this.clientResource = new HttpAsyncRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext())) .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) ); } @Test void testGetRequestAuthentication() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), URIScheme.HTTP); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientResource.start(); final HttpHost target = new HttpHost("localhost", address.getPort()); final HttpRequest request1 = new BasicHttpRequest(Method.GET, target, "/stuff"); final Future> resultFuture1 = requester.execute( new BasicRequestProducer(request1, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message1, CoreMatchers.notNullValue()); final HttpResponse response1 = message1.getHead(); assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_UNAUTHORIZED)); final String body1 = message1.getBody(); assertThat(body1, CoreMatchers.equalTo("You shall not pass!!!")); final HttpRequest request2 = new BasicHttpRequest(Method.GET, target, "/stuff"); request2.setHeader(HttpHeaders.AUTHORIZATION, "let me pass"); final Future> resultFuture2 = requester.execute( new BasicRequestProducer(request2, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message2 = resultFuture2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message2, CoreMatchers.notNullValue()); final HttpResponse response2 = message2.getHead(); assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body2 = message2.getBody(); assertThat(body2, CoreMatchers.equalTo("")); } @Test void testPostRequestAuthentication() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), URIScheme.HTTP); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientResource.start(); final HttpHost target = new HttpHost("localhost", address.getPort()); final Random rnd = new Random(); final byte[] stuff = new byte[10240]; for (int i = 0; i < stuff.length; i++) { stuff[i] = (byte) ('a' + rnd.nextInt(10)); } final HttpRequest request1 = new BasicHttpRequest(Method.POST, target, "/stuff"); final Future> resultFuture1 = requester.execute( new BasicRequestProducer(request1, AsyncEntityProducers.create(stuff, ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message1, CoreMatchers.notNullValue()); final HttpResponse response1 = message1.getHead(); assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_UNAUTHORIZED)); final String body1 = message1.getBody(); assertThat(body1, CoreMatchers.equalTo("You shall not pass!!!")); final HttpRequest request2 = new BasicHttpRequest(Method.POST, target, "/stuff"); request2.setHeader(HttpHeaders.AUTHORIZATION, "let me pass"); final Future> resultFuture2 = requester.execute( new BasicRequestProducer(request2, AsyncEntityProducers.create(stuff, ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message2 = resultFuture2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message2, CoreMatchers.notNullValue()); final HttpResponse response2 = message2.getHead(); assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body2 = message2.getBody(); assertThat(body2, CoreMatchers.equalTo(new String(stuff, StandardCharsets.US_ASCII))); } @Test void testPostRequestAuthenticationNoExpectContinue() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), URIScheme.HTTP); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientResource.start(); final HttpHost target = new HttpHost("localhost", address.getPort()); final Random rnd = new Random(); final byte[] stuff = new byte[10240]; for (int i = 0; i < stuff.length; i++) { stuff[i] = (byte) ('a' + rnd.nextInt(10)); } final HttpRequest request1 = new BasicHttpRequest(Method.POST, target, "/stuff"); request1.setVersion(HttpVersion.HTTP_1_0); final Future> resultFuture1 = requester.execute( new BasicRequestProducer(request1, AsyncEntityProducers.create(stuff, ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message1, CoreMatchers.notNullValue()); final HttpResponse response1 = message1.getHead(); assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_UNAUTHORIZED)); final String body1 = message1.getBody(); assertThat(body1, CoreMatchers.equalTo("You shall not pass!!!")); final HttpRequest request2 = new BasicHttpRequest(Method.POST, target, "/stuff"); request2.setVersion(HttpVersion.HTTP_1_0); request2.setHeader(HttpHeaders.AUTHORIZATION, "let me pass"); final Future> resultFuture2 = requester.execute( new BasicRequestProducer(request2, AsyncEntityProducers.create(stuff, ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message2 = resultFuture2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message2, CoreMatchers.notNullValue()); final HttpResponse response2 = message2.getHead(); assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body2 = message2.getBody(); assertThat(body2, CoreMatchers.equalTo(new String(stuff, StandardCharsets.US_ASCII))); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1CoreTransportTest.java0100644 0000000 0000000 00000023163 00000250600 030761 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import static org.hamcrest.MatcherAssert.assertThat; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.concurrent.Future; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.impl.bootstrap.StandardFilter; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.AsyncFilterChain; import org.apache.hc.core5.http.nio.AsyncPushProducer; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy; import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.extension.nio.HttpAsyncRequesterResource; import org.apache.hc.core5.testing.extension.nio.HttpAsyncServerResource; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; abstract class Http1CoreTransportTest extends HttpCoreTransportTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @RegisterExtension private final HttpAsyncServerResource serverResource; @RegisterExtension private final HttpAsyncRequesterResource clientResource; public Http1CoreTransportTest(final URIScheme scheme) { this(scheme, null); } public Http1CoreTransportTest(final URIScheme scheme, final String tlsProtocol) { super(scheme); this.serverResource = new HttpAsyncServerResource(); this.serverResource.configure(bootstrap -> bootstrap .setTlsStrategy(new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext(tlsProtocol))) .setIOReactorConfig( IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .setTcpKeepIdle(5) .setTcpKeepInterval(3) .build()) .setRequestRouter(RequestRouter.>builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()) .addFilterBefore(StandardFilter.MAIN_HANDLER.name(), "no-keepalive", (request, entityDetails, context, responseTrigger, chain) -> chain.proceed(request, entityDetails, context, new AsyncFilterChain.ResponseTrigger() { @Override public void sendInformation( final HttpResponse response) throws HttpException, IOException { responseTrigger.sendInformation(response); } @Override public void submitResponse( final HttpResponse response, final AsyncEntityProducer entityProducer) throws HttpException, IOException { if (request.getPath().startsWith("/no-keep-alive")) { response.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); } responseTrigger.submitResponse(response, entityProducer); } @Override public void pushPromise( final HttpRequest promise, final AsyncPushProducer responseProducer) throws HttpException, IOException { responseTrigger.pushPromise(promise, responseProducer); } })) ); this.clientResource = new HttpAsyncRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext(tlsProtocol))) .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) ); } @Override HttpAsyncServer serverStart() throws IOException { return serverResource.start(); } @Override HttpAsyncRequester clientStart() { return clientResource.start(); } @Test void testSequentialRequestsNonPersistentConnection() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), scheme); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientResource.start(); final HttpHost target = new HttpHost(scheme.id, "localhost", address.getPort()); final Future> resultFuture1 = requester.execute( new BasicRequestProducer(Method.POST, target, "/no-keep-alive/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message1, CoreMatchers.notNullValue()); final HttpResponse response1 = message1.getHead(); assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body1 = message1.getBody(); assertThat(body1, CoreMatchers.equalTo("some stuff")); final Future> resultFuture2 = requester.execute( new BasicRequestProducer(Method.POST, target, "/no-keep-alive/other-stuff", new StringAsyncEntityProducer("some other stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message2 = resultFuture2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message2, CoreMatchers.notNullValue()); final HttpResponse response2 = message2.getHead(); assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body2 = message2.getBody(); assertThat(body2, CoreMatchers.equalTo("some other stuff")); final Future> resultFuture3 = requester.execute( new BasicRequestProducer(Method.POST, target, "/no-keep-alive/more-stuff", new StringAsyncEntityProducer("some more stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message3 = resultFuture3.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message3, CoreMatchers.notNullValue()); final HttpResponse response3 = message3.getHead(); assertThat(response3.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body3 = message3.getBody(); assertThat(body3, CoreMatchers.equalTo("some more stuff")); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1IntegrationTest.java0100644 0000000 0000000 00000131632 00000250600 030440 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import static org.hamcrest.MatcherAssert.assertThat; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import org.apache.hc.core5.function.Callback; import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.ContentLengthStrategy; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.MalformedChunkCodingException; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.impl.Http1StreamListener; import org.apache.hc.core5.http.impl.HttpProcessors; import org.apache.hc.core5.http.impl.nio.AbstractContentEncoder; import org.apache.hc.core5.http.impl.nio.AbstractMessageWriter; import org.apache.hc.core5.http.impl.nio.DefaultHttpRequestFactory; import org.apache.hc.core5.http.impl.nio.DefaultHttpRequestParser; import org.apache.hc.core5.http.impl.nio.ServerHttp1StreamDuplexer; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.message.BasicLineFormatter; import org.apache.hc.core5.http.message.LineFormatter; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.http.nio.AsyncRequestProducer; import org.apache.hc.core5.http.nio.AsyncResponseProducer; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.AsyncServerRequestHandler; import org.apache.hc.core5.http.nio.ContentEncoder; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.nio.NHttpMessageParser; import org.apache.hc.core5.http.nio.NHttpMessageWriter; import org.apache.hc.core5.http.nio.SessionOutputBuffer; import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers; import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityProducer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer; import org.apache.hc.core5.http.nio.support.AsyncRequestBuilder; import org.apache.hc.core5.http.nio.support.BasicAsyncServerExpectationDecorator; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http.nio.support.BasicResponseProducer; import org.apache.hc.core5.http.nio.support.ImmediateResponseExchangeHandler; import org.apache.hc.core5.http.protocol.DefaultHttpProcessor; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http.protocol.RequestValidateHost; import org.apache.hc.core5.http.support.BasicRequestBuilder; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.extension.nio.Http1TestResources; import org.apache.hc.core5.util.CharArrayBuffer; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; abstract class Http1IntegrationTest extends HttpIntegrationTest { @RegisterExtension private final Http1TestResources resources; public Http1IntegrationTest(final URIScheme scheme) { super(scheme); this.resources = new Http1TestResources(scheme, TIMEOUT); } @Override protected HttpTestServer server() { return resources.server(); } @Override protected HttpTestClient client() { return resources.client(); } @Test void testGetConnectionClose() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); for (int i = 0; i < REQ_NUM; i++) { final Future connectFuture = client.connect(target, TIMEOUT); try (final ClientSessionEndpoint streamEndpoint = connectFuture.get()) { final Future> future = streamEndpoint.execute( AsyncRequestBuilder.get() .setHttpHost(target) .setPath("/hello") .addHeader(HttpHeaders.CONNECTION, "close") .build(), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response1 = result.getHead(); final String entity1 = result.getBody(); Assertions.assertNotNull(response1); Assertions.assertEquals(200, response1.getCode()); Assertions.assertEquals("Hi there", entity1); } } } @Test void testGetIdentityTransfer() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); server.configure(new DefaultHttpProcessor(new RequestValidateHost())); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); for (int i = 0; i < REQ_NUM; i++) { final Future connectFuture = client.connect("localhost", serverEndpoint.getPort(), TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final BasicHttpRequest request = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/hello") .build(); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); streamEndpoint.close(); Assertions.assertNotNull(result); final HttpResponse response = result.getHead(); final String entity = result.getBody(); Assertions.assertNotNull(response); Assertions.assertEquals(200, response.getCode()); Assertions.assertEquals("Hi there", entity); } } @Test void testPostIdentityTransfer() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); server.configure(new DefaultHttpProcessor(new RequestValidateHost())); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); for (int i = 0; i < REQ_NUM; i++) { final Future connectFuture = client.connect("localhost", serverEndpoint.getPort(), TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final BasicHttpRequest request = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/hello") .build(); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, new MultiLineEntityProducer("Hello", 16 * i)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); streamEndpoint.close(); Assertions.assertNotNull(result); final HttpResponse response = result.getHead(); final String entity = result.getBody(); Assertions.assertNotNull(response); Assertions.assertEquals(200, response.getCode()); Assertions.assertEquals("Hi there", entity); } } @Test void testPostIdentityTransferOutOfSequenceResponseNotOK() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); server.register("/hello", () -> new ImmediateResponseExchangeHandler(500, "Go away")); server.configure(new DefaultHttpProcessor(new RequestValidateHost())); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); for (int i = 0; i < REQ_NUM; i++) { final Future connectFuture = client.connect("localhost", serverEndpoint.getPort(), TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final BasicHttpRequest request = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/hello") .build(); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, new MultiLineEntityProducer("Hello", 16 * i)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); streamEndpoint.close(); Assertions.assertNotNull(result); final HttpResponse response = result.getHead(); final String entity = result.getBody(); Assertions.assertNotNull(response); Assertions.assertEquals(500, response.getCode()); Assertions.assertEquals("Go away", entity); } } @Test void testHTTP10Post() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi back")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); for (int i = 0; i < REQ_NUM; i++) { final BasicHttpRequest request = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/hello") .build(); request.setVersion(HttpVersion.HTTP_1_0); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, AsyncEntityProducers.create("Hi there")), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response1 = result.getHead(); final String entity1 = result.getBody(); Assertions.assertNotNull(response1); Assertions.assertEquals(200, response1.getCode()); Assertions.assertEquals("Hi back", entity1); } } @Test void testHTTP11FeaturesDisabledWithHTTP10Requests() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi back")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final BasicHttpRequest request = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/hello") .build(); request.setVersion(HttpVersion.HTTP_1_0); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, new BasicAsyncEntityProducer(new byte[] {'a', 'b', 'c'}, null, true)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final ExecutionException exception = Assertions.assertThrows(ExecutionException.class, future::get); Assertions.assertInstanceOf(ProtocolException.class, exception.getCause()); } @Test void testHeadConnectionClose() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); for (int i = 0; i < REQ_NUM; i++) { final Future connectFuture = client.connect( "localhost", serverEndpoint.getPort(), TIMEOUT); try (final ClientSessionEndpoint streamEndpoint = connectFuture.get()) { final Future> future = streamEndpoint.execute( AsyncRequestBuilder.head() .setHttpHost(target) .setPath("/hello") .addHeader(HttpHeaders.CONNECTION, "close") .build(), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response1 = result.getHead(); Assertions.assertNotNull(response1); Assertions.assertEquals(200, response1.getCode()); Assertions.assertNull(result.getBody()); } } } @Test @Override void testExpectationFailed() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); server.register("*", () -> new MessageExchangeHandler(new StringAsyncEntityConsumer()) { @Override protected void handle( final Message request, final AsyncServerRequestHandler.ResponseTrigger responseTrigger, final HttpContext context) throws IOException, HttpException { responseTrigger.submitResponse(new BasicResponseProducer(HttpStatus.SC_OK, "All is well"), context); } }); server.configure(handler -> new BasicAsyncServerExpectationDecorator(handler) { @Override protected AsyncResponseProducer verify(final HttpRequest request, final HttpContext context) throws IOException, HttpException { final Header h = request.getFirstHeader("password"); if (h != null && "secret".equals(h.getValue())) { return null; } else { return new BasicResponseProducer(HttpStatus.SC_UNAUTHORIZED, "You shall not pass"); } } }); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future sessionFuture = client.requestSession( new HttpHost("localhost", serverEndpoint.getPort()), TIMEOUT, null); final IOSession ioSession = sessionFuture.get(); try (final ClientSessionEndpoint streamEndpoint = new ClientSessionEndpoint(ioSession)) { final BasicHttpRequest request1 = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/echo") .addHeader("password", "secret") .build(); final Future> future1 = streamEndpoint.execute( new BasicRequestProducer(request1, new MultiLineEntityProducer("0123456789abcdef", 1000)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result1 = future1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result1); final HttpResponse response1 = result1.getHead(); Assertions.assertNotNull(response1); Assertions.assertEquals(200, response1.getCode()); Assertions.assertEquals("All is well", result1.getBody()); Assertions.assertTrue(ioSession.isOpen()); final BasicHttpRequest request2 = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/echo") .build(); final Future> future2 = streamEndpoint.execute( new BasicRequestProducer(request2, new MultiLineEntityProducer("0123456789abcdef", 5000)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result2 = future2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result2); final HttpResponse response2 = result2.getHead(); Assertions.assertNotNull(response2); Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response2.getCode()); Assertions.assertEquals("You shall not pass", result2.getBody()); Assertions.assertTrue(ioSession.isOpen()); final BasicHttpRequest request3 = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/echo") .addHeader("password", "secret") .build(); final Future> future3 = streamEndpoint.execute( new BasicRequestProducer(request3, new MultiLineEntityProducer("0123456789abcdef", 1000)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result3 = future3.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result3); final HttpResponse response3 = result3.getHead(); Assertions.assertNotNull(response3); Assertions.assertEquals(200, response3.getCode()); Assertions.assertEquals("All is well", result3.getBody()); Assertions.assertTrue(ioSession.isOpen()); final BasicHttpRequest request4 = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/echo") .build(); final Future> future4 = streamEndpoint.execute( new BasicRequestProducer(request4, AsyncEntityProducers.create("blah")), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result4 = future4.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result4); final HttpResponse response4 = result4.getHead(); Assertions.assertNotNull(response4); Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response4.getCode()); Assertions.assertEquals("You shall not pass", result4.getBody()); Assertions.assertFalse(ioSession.isOpen()); } } @Test void testExpectationFailedCloseConnection() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); server.register("*", () -> new MessageExchangeHandler(new StringAsyncEntityConsumer()) { @Override protected void handle( final Message request, final AsyncServerRequestHandler.ResponseTrigger responseTrigger, final HttpContext context) throws IOException, HttpException { responseTrigger.submitResponse(new BasicResponseProducer(HttpStatus.SC_OK, "All is well"), context); } }); server.configure(handler -> new BasicAsyncServerExpectationDecorator(handler) { @Override protected AsyncResponseProducer verify(final HttpRequest request, final HttpContext context) throws IOException, HttpException { final Header h = request.getFirstHeader("password"); if (h != null && "secret".equals(h.getValue())) { return null; } else { final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_UNAUTHORIZED); response.addHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); return new BasicResponseProducer(response, "You shall not pass"); } } }); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future sessionFuture = client.requestSession( new HttpHost("localhost", serverEndpoint.getPort()), TIMEOUT, null); final IOSession ioSession = sessionFuture.get(); try (final ClientSessionEndpoint streamEndpoint = new ClientSessionEndpoint(ioSession)) { final BasicHttpRequest request1 = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/echo") .build(); final Future> future1 = streamEndpoint.execute( new BasicRequestProducer(request1, new MultiBinEntityProducer( new byte[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}, 100000, ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result1 = future1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result1); final HttpResponse response1 = result1.getHead(); Assertions.assertNotNull(response1); Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response1.getCode()); Assertions.assertEquals("You shall not pass", result1.getBody()); Assertions.assertFalse(streamEndpoint.isOpen()); } } @Test void testMissingExpectContinueAckClientContinues() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); // Disable 100-continue handshake on the server side server.configure(handler -> handler); server.register("/hello", () -> new SingleLineResponseHandler("Hi there back")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.configure(Http1Config.custom() .setWaitForContinueTimeout(Timeout.ofMilliseconds(100)) .build()); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); for (int i = 0; i < REQ_NUM; i++) { final BasicHttpRequest request = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/hello") .build(); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, AsyncEntityProducers.create("Hi there")), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response1 = result.getHead(); final String entity1 = result.getBody(); Assertions.assertNotNull(response1); Assertions.assertEquals(200, response1.getCode()); Assertions.assertEquals("Hi there back", entity1); } } @Test void testSlowResponseConsumer() throws Exception { final Http1TestClient client = resources.client(); client.configure(Http1Config.custom() .setBufferSize(256) .build()); client.start(); super.testSlowResponseConsumer(); } @Test void testSlowResponseProducer() throws Exception { final Http1TestClient client = resources.client(); client.configure(Http1Config.custom() .setBufferSize(256) .build()); client.start(); super.testSlowResponseProducer(); } @Test void testPipelinedConnectionClose() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); server.register("/hello*", () -> new SingleLineResponseHandler("Hi back")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final BasicHttpRequest request1 = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/hello-1") .build(); final Future> future1 = streamEndpoint.execute( new BasicRequestProducer(request1, AsyncEntityProducers.create("Hi there")), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final BasicHttpRequest request2 = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/hello-2") .addHeader(HttpHeaders.CONNECTION, "close") .build(); final Future> future2 = streamEndpoint.execute( new BasicRequestProducer(request2, AsyncEntityProducers.create("Hi there")), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final BasicHttpRequest request3 = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/hello-3") .build(); final Future> future3 = streamEndpoint.execute( new BasicRequestProducer(request3, AsyncEntityProducers.create("Hi there")), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result1 = future1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result1); final HttpResponse response1 = result1.getHead(); final String entity1 = result1.getBody(); Assertions.assertNotNull(response1); Assertions.assertEquals(200, response1.getCode()); Assertions.assertEquals("Hi back", entity1); final Message result2 = future2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result2); final HttpResponse response2 = result2.getHead(); final String entity2 = result2.getBody(); Assertions.assertNotNull(response2); Assertions.assertEquals(200, response2.getCode()); Assertions.assertEquals("Hi back", entity2); final Exception exception = Assertions.assertThrows(Exception.class, () -> future3.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); assertThat(exception, CoreMatchers.anyOf( CoreMatchers.instanceOf(CancellationException.class), CoreMatchers.instanceOf(ExecutionException.class))); final BasicHttpRequest request4 = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/hello-3") .build(); final Future> future4 = streamEndpoint.execute( new BasicRequestProducer(request4, AsyncEntityProducers.create("Hi there")), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Exception exception2 = Assertions.assertThrows(Exception.class, () -> future4.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); assertThat(exception2, CoreMatchers.anyOf( CoreMatchers.instanceOf(CancellationException.class), CoreMatchers.instanceOf(ExecutionException.class))); } @Test void testPipelinedInvalidRequest() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); server.register("/hello*", () -> new SingleLineResponseHandler("Hi back")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final BasicHttpRequest request1 = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/hello-1") .build(); final Future> future1 = streamEndpoint.execute( new BasicRequestProducer(request1, AsyncEntityProducers.create("Hi there")), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final BasicHttpRequest request2 = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/hello-2") .addHeader(HttpHeaders.HOST, "blah:blah") .build(); final Future> future2 = streamEndpoint.execute( new BasicRequestProducer(request2, AsyncEntityProducers.create("Hi there")), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final BasicHttpRequest request3 = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/hello-3") .build(); final Future> future3 = streamEndpoint.execute( new BasicRequestProducer(request3, AsyncEntityProducers.create("Hi there")), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result1 = future1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result1); final HttpResponse response1 = result1.getHead(); final String entity1 = result1.getBody(); Assertions.assertNotNull(response1); Assertions.assertEquals(200, response1.getCode()); Assertions.assertEquals("Hi back", entity1); final Message result2 = future2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result2); final HttpResponse response2 = result2.getHead(); final String entity2 = result2.getBody(); Assertions.assertNotNull(response2); Assertions.assertEquals(400, response2.getCode()); Assertions.assertFalse(entity2.isEmpty()); final Exception exception = Assertions.assertThrows(Exception.class, () -> future3.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); assertThat(exception, CoreMatchers.anyOf( CoreMatchers.instanceOf(CancellationException.class), CoreMatchers.instanceOf(ExecutionException.class))); } private static final byte[] GARBAGE = "garbage".getBytes(StandardCharsets.US_ASCII); private static class BrokenChunkEncoder extends AbstractContentEncoder { private final CharArrayBuffer lineBuffer; private boolean done; BrokenChunkEncoder( final WritableByteChannel channel, final SessionOutputBuffer buffer, final BasicHttpTransportMetrics metrics) { super(channel, buffer, metrics); lineBuffer = new CharArrayBuffer(16); } @Override public void complete(final List trailers) throws IOException { super.complete(trailers); } @Override public int write(final ByteBuffer src) throws IOException { if (!channel().isOpen()) { return 0; } final int chunk; if (!done) { lineBuffer.clear(); lineBuffer.append(Integer.toHexString(GARBAGE.length * 10)); buffer().writeLine(lineBuffer); buffer().write(ByteBuffer.wrap(GARBAGE)); done = true; chunk = GARBAGE.length; } else { chunk = 0; } final long bytesWritten = buffer().flush(channel()); if (bytesWritten > 0) { metrics().incrementBytesTransferred(bytesWritten); } if (!buffer().hasData()) { channel().close(); } return chunk; } } @Test void testTruncatedChunk() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); final InetSocketAddress serverEndpoint = server.startExecution(new InternalServerHttp1EventHandlerFactory( HttpProcessors.server(), (request, context) -> new MessageExchangeHandler(new StringAsyncEntityConsumer()) { @Override protected void handle( final Message request, final AsyncServerRequestHandler.ResponseTrigger responseTrigger, final HttpContext context) throws IOException, HttpException { responseTrigger.submitResponse( new BasicResponseProducer(new StringAsyncEntityProducer("useful stuff")), context); } }, Http1Config.DEFAULT, CharCodingConfig.DEFAULT, DefaultConnectionReuseStrategy.INSTANCE, null, null, scheme == URIScheme.HTTPS ? SSLTestContexts.createServerSSLContext() : null, null, null) { @Override protected ServerHttp1StreamDuplexer createServerHttp1StreamDuplexer( final ProtocolIOSession ioSession, final HttpProcessor httpProcessor, final HandlerFactory exchangeHandlerFactory, final Http1Config http1Config, final CharCodingConfig connectionConfig, final ConnectionReuseStrategy connectionReuseStrategy, final NHttpMessageParser incomingMessageParser, final NHttpMessageWriter outgoingMessageWriter, final ContentLengthStrategy incomingContentStrategy, final ContentLengthStrategy outgoingContentStrategy, final Http1StreamListener streamListener, final Callback exceptionCallback) { return new ServerHttp1StreamDuplexer(ioSession, httpProcessor, exchangeHandlerFactory, scheme.id, http1Config, connectionConfig, connectionReuseStrategy, incomingMessageParser, outgoingMessageWriter, incomingContentStrategy, outgoingContentStrategy, streamListener, exceptionCallback) { @Override protected ContentEncoder createContentEncoder( final long len, final WritableByteChannel channel, final SessionOutputBuffer buffer, final BasicHttpTransportMetrics metrics) throws HttpException { if (len == ContentLengthStrategy.CHUNKED) { return new BrokenChunkEncoder(channel, buffer, metrics); } else { return super.createContentEncoder(len, channel, buffer, metrics); } } }; } }); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final BasicHttpRequest request = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/hello") .build(); final AsyncRequestProducer requestProducer = new BasicRequestProducer(request, null); final StringAsyncEntityConsumer entityConsumer = new StringAsyncEntityConsumer() { @Override public void releaseResources() { // Do not clear internal content buffer } }; final BasicResponseConsumer responseConsumer = new BasicResponseConsumer<>(entityConsumer); final Future> future1 = streamEndpoint.execute(requestProducer, responseConsumer, null); final ExecutionException exception = Assertions.assertThrows(ExecutionException.class, () -> future1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); final Throwable cause = exception.getCause(); Assertions.assertTrue(cause instanceof MalformedChunkCodingException); Assertions.assertEquals("garbage", entityConsumer.generateContent()); } @ParameterizedTest @ValueSource(strings = {"GET", "POST"}) void testHeaderTooLarge(final String method) throws Exception { final Http1TestServer server = resources.server(); server.configure(Http1Config.custom() .setMaxLineLength(100) .build()); super.testHeaderTooLarge(method); } @Test void testInvalidRequestMessage() throws Exception { final Http1Config http1Config = Http1Config.DEFAULT; final Http1TestServer server = resources.server(); server.configure(http1Config); server.configure(() -> new DefaultHttpRequestParser(http1Config, DefaultHttpRequestFactory.INSTANCE) { @Override protected HttpRequest createMessage(final CharArrayBuffer buffer) throws HttpException { throw new RuntimeException("Ka-boom"); } }); server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); final Http1TestClient client = resources.client(); client.start(); final Future connectFuture = client.connect( "localhost", serverEndpoint.getPort(), TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final BasicHttpRequest request = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/hello") .build(); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final ExecutionException executionException = Assertions.assertThrows(ExecutionException.class, () -> future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); Assertions.assertInstanceOf(ConnectionClosedException.class, executionException.getCause()); } @Test void testInvalidProtocolVersion() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); final LineFormatter lineFormatter = BasicLineFormatter.INSTANCE; client.configure(() -> new AbstractMessageWriter(lineFormatter) { @Override protected void writeHeadLine(final HttpRequest message, final CharArrayBuffer lineBuf) throws IOException { lineBuf.clear(); lineFormatter.formatRequestLine(lineBuf, new RequestLine( message.getMethod(), message.getRequestUri(), new HttpVersion(2, 1))); } }); client.start(); final Future connectFuture = client.connect( "localhost", serverEndpoint.getPort(), TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final BasicHttpRequest request = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/hello") .build(); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response = result.getHead(); Assertions.assertNotNull(response); Assertions.assertEquals(505, response.getCode()); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/HttpCoreTransportTest.java0100644 0000000 0000000 00000042334 00000250600 030701 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import static org.hamcrest.MatcherAssert.assertThat; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.Future; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; abstract class HttpCoreTransportTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); final URIScheme scheme; public HttpCoreTransportTest(final URIScheme scheme) { this.scheme = scheme; } abstract HttpAsyncServer serverStart() throws IOException; abstract HttpAsyncRequester clientStart() throws IOException; @Test void testSequentialRequests() throws Exception { final HttpAsyncServer server = serverStart(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), scheme); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientStart(); final HttpHost target = new HttpHost(scheme.id, "localhost", address.getPort()); final Future> resultFuture1 = requester.execute( new BasicRequestProducer(Method.POST, target, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message1, CoreMatchers.notNullValue()); final HttpResponse response1 = message1.getHead(); assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body1 = message1.getBody(); assertThat(body1, CoreMatchers.equalTo("some stuff")); final Future> resultFuture2 = requester.execute( new BasicRequestProducer(Method.POST, target, "/other-stuff", new StringAsyncEntityProducer("some other stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message2 = resultFuture2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message2, CoreMatchers.notNullValue()); final HttpResponse response2 = message2.getHead(); assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body2 = message2.getBody(); assertThat(body2, CoreMatchers.equalTo("some other stuff")); final Future> resultFuture3 = requester.execute( new BasicRequestProducer(Method.POST, target, "/more-stuff", new StringAsyncEntityProducer("some more stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message3 = resultFuture3.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message3, CoreMatchers.notNullValue()); final HttpResponse response3 = message3.getHead(); assertThat(response3.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body3 = message3.getBody(); assertThat(body3, CoreMatchers.equalTo("some more stuff")); } @Test void testLargeRequest() throws Exception { final HttpAsyncServer server = serverStart(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), scheme); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientStart(); final HttpHost target = new HttpHost(scheme.id, "localhost", address.getPort()); final String content = IntStream.range(0, 1000).mapToObj(i -> "a lot of stuff").collect(Collectors.joining(" ")); final Future> resultFuture = requester.execute( new BasicRequestProducer(Method.POST, target, "/a-lot-of-stuff", AsyncEntityProducers.create(content, ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message = resultFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message, CoreMatchers.notNullValue()); final HttpResponse response = message.getHead(); assertThat(response.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body = message.getBody(); assertThat(body, CoreMatchers.equalTo(content)); } @Test void testSequentialRequestsNonPersistentConnection() throws Exception { final HttpAsyncServer server = serverStart(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), scheme); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientStart(); final HttpHost target = new HttpHost(scheme.id, "localhost", address.getPort()); final Future> resultFuture1 = requester.execute( new BasicRequestProducer(Method.POST, target, "/no-keep-alive/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message1, CoreMatchers.notNullValue()); final HttpResponse response1 = message1.getHead(); assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body1 = message1.getBody(); assertThat(body1, CoreMatchers.equalTo("some stuff")); final Future> resultFuture2 = requester.execute( new BasicRequestProducer(Method.POST, target, "/no-keep-alive/other-stuff", new StringAsyncEntityProducer("some other stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message2 = resultFuture2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message2, CoreMatchers.notNullValue()); final HttpResponse response2 = message2.getHead(); assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body2 = message2.getBody(); assertThat(body2, CoreMatchers.equalTo("some other stuff")); final Future> resultFuture3 = requester.execute( new BasicRequestProducer(Method.POST, target, "/no-keep-alive/more-stuff", new StringAsyncEntityProducer("some more stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message3 = resultFuture3.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message3, CoreMatchers.notNullValue()); final HttpResponse response3 = message3.getHead(); assertThat(response3.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body3 = message3.getBody(); assertThat(body3, CoreMatchers.equalTo("some more stuff")); } @Test void testSequentialRequestsSameEndpoint() throws Exception { final HttpAsyncServer server = serverStart(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), scheme); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientStart(); final HttpHost target = new HttpHost(scheme.id, "localhost", address.getPort()); final Future endpointFuture = requester.connect(target, Timeout.ofSeconds(5)); final AsyncClientEndpoint endpoint = endpointFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); try { final Future> resultFuture1 = endpoint.execute( new BasicRequestProducer(Method.POST, target, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message1, CoreMatchers.notNullValue()); final HttpResponse response1 = message1.getHead(); assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body1 = message1.getBody(); assertThat(body1, CoreMatchers.equalTo("some stuff")); final Future> resultFuture2 = endpoint.execute( new BasicRequestProducer(Method.POST, target, "/other-stuff", new StringAsyncEntityProducer("some other stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message message2 = resultFuture2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message2, CoreMatchers.notNullValue()); final HttpResponse response2 = message2.getHead(); assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body2 = message2.getBody(); assertThat(body2, CoreMatchers.equalTo("some other stuff")); final Future> resultFuture3 = endpoint.execute( new BasicRequestProducer(Method.POST, target, "/more-stuff", new StringAsyncEntityProducer("some more stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message message3 = resultFuture3.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message3, CoreMatchers.notNullValue()); final HttpResponse response3 = message3.getHead(); assertThat(response3.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body3 = message3.getBody(); assertThat(body3, CoreMatchers.equalTo("some more stuff")); } finally { endpoint.releaseAndReuse(); } } @Test void testPipelinedRequests() throws Exception { final HttpAsyncServer server = serverStart(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), scheme); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientStart(); final HttpHost target = new HttpHost(scheme.id, "localhost", address.getPort()); final Future endpointFuture = requester.connect(target, Timeout.ofSeconds(5)); final AsyncClientEndpoint endpoint = endpointFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); try { final Queue>> queue = new LinkedList<>(); queue.add(endpoint.execute( new BasicRequestProducer(Method.POST, target, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null)); queue.add(endpoint.execute( new BasicRequestProducer(Method.POST, target, "/other-stuff", new StringAsyncEntityProducer("some other stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null)); queue.add(endpoint.execute( new BasicRequestProducer(Method.POST, target, "/more-stuff", new StringAsyncEntityProducer("some more stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null)); while (!queue.isEmpty()) { final Future> resultFuture = queue.remove(); final Message message = resultFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message, CoreMatchers.notNullValue()); final HttpResponse response = message.getHead(); assertThat(response.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body = message.getBody(); assertThat(body, CoreMatchers.containsString("stuff")); } } finally { endpoint.releaseAndReuse(); } } @Test void testNonPersistentHeads() throws Exception { final HttpAsyncServer server = serverStart(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), scheme); final ListenerEndpoint listener = future.get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientStart(); final HttpHost target = new HttpHost(scheme.id, "localhost", address.getPort()); final Queue>> queue = new LinkedList<>(); for (int i = 0; i < 20; i++) { final HttpRequest head = new BasicHttpRequest(Method.HEAD, target, "/no-keep-alive/stuff?p=" + i); queue.add(requester.execute( new BasicRequestProducer(head, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null)); } while (!queue.isEmpty()) { final Future> resultFuture = queue.remove(); final Message message = resultFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message, CoreMatchers.notNullValue()); final HttpResponse response = message.getHead(); assertThat(response.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); assertThat(message.getBody(), CoreMatchers.nullValue()); } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/HttpIntegrationTest.java0100644 0000000 0000000 00000203477 00000250600 030366 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStreamWriter; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Random; import java.util.StringTokenizer; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.io.HttpRequestHandler; import org.apache.hc.core5.http.io.entity.EntityTemplate; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.nio.AsyncRequestConsumer; import org.apache.hc.core5.http.nio.AsyncRequestProducer; import org.apache.hc.core5.http.nio.AsyncResponseProducer; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.AsyncServerRequestHandler; import org.apache.hc.core5.http.nio.CapacityChannel; import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.RequestChannel; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers; import org.apache.hc.core5.http.nio.entity.DigestingEntityConsumer; import org.apache.hc.core5.http.nio.entity.DigestingEntityProducer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer; import org.apache.hc.core5.http.nio.support.AbstractServerExchangeHandler; import org.apache.hc.core5.http.nio.support.AsyncResponseBuilder; import org.apache.hc.core5.http.nio.support.BasicAsyncServerExpectationDecorator; import org.apache.hc.core5.http.nio.support.BasicRequestConsumer; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http.nio.support.BasicResponseProducer; import org.apache.hc.core5.http.nio.support.ImmediateResponseExchangeHandler; import org.apache.hc.core5.http.nio.support.classic.ClassicToAsyncRequestProducer; import org.apache.hc.core5.http.nio.support.classic.ClassicToAsyncResponseConsumer; import org.apache.hc.core5.http.nio.support.classic.ClassicToAsyncServerExchangeHandler; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.support.BasicRequestBuilder; import org.apache.hc.core5.testing.extension.ExecutorResource; import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; abstract class HttpIntegrationTest { static final Timeout TIMEOUT = Timeout.ofMinutes(1); static final Timeout LONG_TIMEOUT = Timeout.ofMinutes(2); static final int REQ_NUM = 5; final URIScheme scheme; @RegisterExtension final ExecutorResource executorResource; public HttpIntegrationTest(final URIScheme scheme) { this.scheme = scheme; this.executorResource = new ExecutorResource(5); } HttpHost target(final InetSocketAddress serverEndpoint) { return new HttpHost(scheme.id, null, "localhost", serverEndpoint.getPort()); } protected abstract HttpTestServer server(); protected abstract HttpTestClient client(); @Test void testGet() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); for (int i = 0; i < REQ_NUM; i++) { final BasicHttpRequest request = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/hello") .build(); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response1 = result.getHead(); final String entity1 = result.getBody(); Assertions.assertNotNull(response1); Assertions.assertEquals(200, response1.getCode()); Assertions.assertEquals("Hi there", entity1); } } @Test void testGetsPipelined() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final Queue>> queue = new LinkedList<>(); for (int i = 0; i < REQ_NUM; i++) { final BasicHttpRequest request = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/hello") .build(); queue.add(streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null)); } while (!queue.isEmpty()) { final Future> future = queue.remove(); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response = result.getHead(); final String entity = result.getBody(); Assertions.assertNotNull(response); Assertions.assertEquals(200, response.getCode()); Assertions.assertEquals("Hi there", entity); } } @Test void testLargeGet() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/", () -> new MultiLineResponseHandler("0123456789abcdef", 5000)); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); for (int i = 0; i < REQ_NUM; i++) { final BasicHttpRequest request = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/") .build(); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response = result.getHead(); Assertions.assertNotNull(response); Assertions.assertEquals(200, response.getCode()); final String s = result.getBody(); Assertions.assertNotNull(s); final StringTokenizer t = new StringTokenizer(s, "\r\n"); while (t.hasMoreTokens()) { Assertions.assertEquals("0123456789abcdef", t.nextToken()); } } } @Test void testLargeGetsPipelined() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/", () -> new MultiLineResponseHandler("0123456789abcdef", 2000)); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final Queue>> queue = new LinkedList<>(); for (int i = 0; i < REQ_NUM; i++) { final BasicHttpRequest request = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/") .build(); queue.add(streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null)); } while (!queue.isEmpty()) { final Future> future = queue.remove(); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response = result.getHead(); Assertions.assertNotNull(response); Assertions.assertEquals(200, response.getCode()); final String entity = result.getBody(); Assertions.assertNotNull(entity); final StringTokenizer t = new StringTokenizer(entity, "\r\n"); while (t.hasMoreTokens()) { Assertions.assertEquals("0123456789abcdef", t.nextToken()); } } } @Test void testPost() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi back")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); for (int i = 0; i < REQ_NUM; i++) { final BasicHttpRequest request = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/hello") .build(); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, AsyncEntityProducers.create("Hi there")), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response1 = result.getHead(); final String entity1 = result.getBody(); Assertions.assertNotNull(response1); Assertions.assertEquals(200, response1.getCode()); Assertions.assertEquals("Hi back", entity1); } } @Test void testPostPipelined() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi back")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final Queue>> queue = new LinkedList<>(); for (int i = 0; i < REQ_NUM; i++) { final BasicHttpRequest request = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/hello") .build(); queue.add(streamEndpoint.execute( new BasicRequestProducer(request, AsyncEntityProducers.create("Hi there")), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null)); } while (!queue.isEmpty()) { final Future> future = queue.remove(); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response = result.getHead(); final String entity = result.getBody(); Assertions.assertNotNull(response); Assertions.assertEquals(200, response.getCode()); Assertions.assertEquals("Hi back", entity); } } @Test void testLargePostsPipelined() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("*", () -> new EchoHandler(2048)); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final Queue>> queue = new LinkedList<>(); for (int i = 0; i < REQ_NUM; i++) { final BasicHttpRequest request = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/echo") .build(); queue.add(streamEndpoint.execute( new BasicRequestProducer(request, new MultiLineEntityProducer("0123456789abcdef", 5000)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null)); } while (!queue.isEmpty()) { final Future> future = queue.remove(); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response = result.getHead(); Assertions.assertNotNull(response); Assertions.assertEquals(200, response.getCode()); final String entity = result.getBody(); Assertions.assertNotNull(entity); final StringTokenizer t = new StringTokenizer(entity, "\r\n"); while (t.hasMoreTokens()) { Assertions.assertEquals("0123456789abcdef", t.nextToken()); } } } @Test void testNoEntityPost() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi back")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); for (int i = 0; i < REQ_NUM; i++) { final BasicHttpRequest request = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/hello") .build(); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response1 = result.getHead(); final String entity1 = result.getBody(); Assertions.assertNotNull(response1); Assertions.assertEquals(200, response1.getCode()); Assertions.assertEquals("Hi back", entity1); } } @Test void testPostOutOfSequenceResponseOK() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/hello", () -> new ImmediateResponseExchangeHandler(200, "Welcome")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect("localhost", serverEndpoint.getPort(), TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); for (int i = 0; i < REQ_NUM; i++) { final BasicHttpRequest request = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/hello") .build(); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, new MultiLineEntityProducer("Hello", 512 * i)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response = result.getHead(); final String entity = result.getBody(); Assertions.assertNotNull(response); Assertions.assertEquals(200, response.getCode()); Assertions.assertEquals("Welcome", entity); } } @Test void testHead() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); for (int i = 0; i < REQ_NUM; i++) { final BasicHttpRequest request = BasicRequestBuilder.head() .setHttpHost(target) .setPath("/hello") .build(); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response1 = result.getHead(); Assertions.assertNotNull(response1); Assertions.assertEquals(200, response1.getCode()); Assertions.assertNull(result.getBody()); } } @Test void testHeadPipelined() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final Queue>> queue = new LinkedList<>(); for (int i = 0; i < REQ_NUM; i++) { final BasicHttpRequest request = BasicRequestBuilder.head() .setHttpHost(target) .setPath("/hello") .build(); queue.add(streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null)); } while (!queue.isEmpty()) { final Future> future = queue.remove(); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response1 = result.getHead(); Assertions.assertNotNull(response1); Assertions.assertEquals(200, response1.getCode()); Assertions.assertNull(result.getBody()); } } @Test void testSlowResponseConsumer() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/", () -> new MultiLineResponseHandler("0123456789abcd", 3)); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final ClassicHttpRequest request = ClassicRequestBuilder.get() .setHttpHost(target) .setPath("/") .build(); final ClassicToAsyncResponseConsumer responseConsumer = new ClassicToAsyncResponseConsumer(16, TIMEOUT); streamEndpoint.execute( new BasicRequestProducer(request, null), responseConsumer, null); final Random random = new Random(); try (ClassicHttpResponse response = responseConsumer.blockWaiting()) { final HttpEntity entity = response.getEntity(); final ContentType contentType = ContentType.parse(entity.getContentType()); final Charset charset = ContentType.getCharset(contentType, StandardCharsets.UTF_8); try (final InputStream inputStream = entity.getContent()) { final StringBuilder buffer = new StringBuilder(); final byte[] tmp = new byte[16]; int l; while ((l = inputStream.read(tmp)) != -1) { buffer.append(charset.decode(ByteBuffer.wrap(tmp, 0, l))); Thread.sleep(100 + random.nextInt(400)); } final StringTokenizer t1 = new StringTokenizer(buffer.toString(), "\r\n"); while (t1.hasMoreTokens()) { Assertions.assertEquals("0123456789abcd", t1.nextToken()); } } } } @Test void testSlowRequestProducer() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("*", () -> new EchoHandler(2048)); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final Random random = new Random(); final ClassicHttpRequest request1 = ClassicRequestBuilder.post() .setHttpHost(target) .setPath("/echo") .setEntity(new EntityTemplate( -1, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8), null, outputStream -> { try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8))) { for (int i = 0; i < 500; i++) { if (i % 100 == 0) { writer.flush(); Thread.sleep(100 + random.nextInt(400)); } writer.write("0123456789abcdef\r\n"); } } catch (final InterruptedException ex) { Thread.currentThread().interrupt(); throw new InterruptedIOException(ex.getMessage()); } })) .build(); final ClassicToAsyncRequestProducer requestProducer = new ClassicToAsyncRequestProducer(request1, 16, TIMEOUT); final Future> future1 = streamEndpoint.execute( requestProducer, new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); requestProducer.blockWaiting().execute(); final Message result1 = future1.get(LONG_TIMEOUT.getDuration(), LONG_TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result1); final HttpResponse response1 = result1.getHead(); Assertions.assertNotNull(response1); Assertions.assertEquals(200, response1.getCode()); final String s1 = result1.getBody(); Assertions.assertNotNull(s1); final StringTokenizer t1 = new StringTokenizer(s1, "\r\n"); while (t1.hasMoreTokens()) { Assertions.assertEquals("0123456789abcdef", t1.nextToken()); } } @Test void testSlowResponseProducer() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); final Random random = new Random(); final HttpRequestHandler requestHandler = (request, response, context) -> { final HttpEntity requestEntity = request.getEntity(); if (requestEntity != null) { EntityUtils.consume(requestEntity); } final HttpEntity responseEntity = new EntityTemplate( ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8), outputStream -> { try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8))) { for (int i = 0; i < 500; i++) { if (i % 100 == 0) { writer.flush(); Thread.sleep(100 + random.nextInt(400)); } writer.write("0123456789abcdef\r\n"); } } catch (final InterruptedException ex) { Thread.currentThread().interrupt(); throw new InterruptedIOException(ex.getMessage()); } }); response.setEntity(responseEntity); }; final RequestRouter requestRouter = RequestRouter.builder() .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .addRoute(RequestRouter.LOCAL_AUTHORITY, "/hello", requestHandler) .build(); server.register("*", () -> new ClassicToAsyncServerExchangeHandler( Executors.newSingleThreadExecutor(), requestRouter, LoggingExceptionCallback.INSTANCE)); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final BasicHttpRequest request1 = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/hello") .build(); final Future> future1 = streamEndpoint.execute( new BasicRequestProducer(request1, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result1 = future1.get(LONG_TIMEOUT.getDuration(), LONG_TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result1); final HttpResponse response1 = result1.getHead(); Assertions.assertNotNull(response1); Assertions.assertEquals(200, response1.getCode()); final String s1 = result1.getBody(); Assertions.assertNotNull(s1); final StringTokenizer t1 = new StringTokenizer(s1, "\r\n"); while (t1.hasMoreTokens()) { Assertions.assertEquals("0123456789abcdef", t1.nextToken()); } } @Test void testPrematureResponse() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("*", () -> new AsyncServerExchangeHandler() { private final AtomicReference responseProducer = new AtomicReference<>(); @Override public void handleRequest( final HttpRequest request, final EntityDetails entityDetails, final ResponseChannel responseChannel, final HttpContext context) throws HttpException, IOException { final AsyncResponseProducer producer; final Header h = request.getFirstHeader("password"); if (h != null && "secret".equals(h.getValue())) { producer = new BasicResponseProducer(HttpStatus.SC_OK, "All is well"); } else { producer = new BasicResponseProducer(HttpStatus.SC_UNAUTHORIZED, "You shall not pass"); } responseProducer.set(producer); producer.sendResponse(responseChannel, context); } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { capacityChannel.update(Integer.MAX_VALUE); } @Override public void consume(final ByteBuffer src) throws IOException { } @Override public void streamEnd(final List trailers) throws HttpException, IOException { } @Override public int available() { final AsyncResponseProducer producer = responseProducer.get(); return producer.available(); } @Override public void produce(final DataStreamChannel channel) throws IOException { final AsyncResponseProducer producer = responseProducer.get(); producer.produce(channel); } @Override public void failed(final Exception cause) { } @Override public void releaseResources() { } }); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); for (int i = 0; i < 3; i++) { final BasicHttpRequest request1 = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/echo") .build(); final Future> future1 = streamEndpoint.execute( new BasicRequestProducer(request1, new MultiLineEntityProducer("0123456789abcdef", 100000)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result1 = future1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result1); final HttpResponse response1 = result1.getHead(); Assertions.assertNotNull(response1); Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response1.getCode()); Assertions.assertEquals("You shall not pass", result1.getBody()); Assertions.assertTrue(streamEndpoint.isOpen()); } final BasicHttpRequest request1 = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/echo") .build(); final Future> future1 = streamEndpoint.execute( new BasicRequestProducer(request1, new MultiBinEntityProducer( new byte[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}, 100000, ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result1 = future1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result1); final HttpResponse response1 = result1.getHead(); Assertions.assertNotNull(response1); Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response1.getCode()); Assertions.assertEquals("You shall not pass", result1.getBody()); } @Test void testExpectationFailed() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("*", () -> new MessageExchangeHandler(new StringAsyncEntityConsumer()) { @Override protected void handle( final Message request, final AsyncServerRequestHandler.ResponseTrigger responseTrigger, final HttpContext context) throws IOException, HttpException { responseTrigger.submitResponse(new BasicResponseProducer(HttpStatus.SC_OK, "All is well"), context); } }); server.configure(handler -> new BasicAsyncServerExpectationDecorator(handler) { @Override protected AsyncResponseProducer verify(final HttpRequest request, final HttpContext context) throws IOException, HttpException { final Header h = request.getFirstHeader("password"); if (h != null && "secret".equals(h.getValue())) { return null; } else { return new BasicResponseProducer(HttpStatus.SC_UNAUTHORIZED, "You shall not pass"); } } }); server.configure(handler -> new BasicAsyncServerExpectationDecorator(handler) { @Override protected AsyncResponseProducer verify(final HttpRequest request, final HttpContext context) throws IOException, HttpException { final Header h = request.getFirstHeader("password"); if (h != null && "secret".equals(h.getValue())) { return null; } else { return new BasicResponseProducer(HttpStatus.SC_UNAUTHORIZED, "You shall not pass"); } } }); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final BasicHttpRequest request1 = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/echo") .addHeader("password", "secret") .build(); final Future> future1 = streamEndpoint.execute( new BasicRequestProducer(request1, new MultiLineEntityProducer("0123456789abcdef", 5000)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result1 = future1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result1); final HttpResponse response1 = result1.getHead(); Assertions.assertNotNull(response1); Assertions.assertEquals(200, response1.getCode()); Assertions.assertEquals("All is well", result1.getBody()); final BasicHttpRequest request2 = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/echo") .build(); final Future> future2 = streamEndpoint.execute( new BasicRequestProducer(request2, new MultiLineEntityProducer("0123456789abcdef", 5000)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result2 = future2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result2); final HttpResponse response2 = result2.getHead(); Assertions.assertNotNull(response2); Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response2.getCode()); Assertions.assertEquals("You shall not pass", result2.getBody()); } @Test void testDelayedExpectContinueAck() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); // Disable 100-continue handshake on the server side server.configure(handler -> handler); server.register("*", () -> new AsyncServerExchangeHandler() { private final Random random = new Random(System.currentTimeMillis()); private final AsyncEntityProducer entityProducer = AsyncEntityProducers.create( "All is well"); @Override public void handleRequest( final HttpRequest request, final EntityDetails entityDetails, final ResponseChannel responseChannel, final HttpContext context) throws HttpException, IOException { executorResource.getExecutorService().execute(() -> { try { if (entityDetails != null) { final Header h = request.getFirstHeader(HttpHeaders.EXPECT); if (h != null && HeaderElements.CONTINUE.equalsIgnoreCase(h.getValue())) { Thread.sleep(random.nextInt(1000)); responseChannel.sendInformation(new BasicHttpResponse(HttpStatus.SC_CONTINUE), context); } final HttpResponse response = new BasicHttpResponse(200); responseChannel.sendResponse(response, entityProducer, context); } } catch (final Exception ignore) { // ignore } }); } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { capacityChannel.update(Integer.MAX_VALUE); } @Override public void consume(final ByteBuffer src) throws IOException { } @Override public void streamEnd(final List trailers) throws HttpException, IOException { } @Override public int available() { return entityProducer.available(); } @Override public void produce(final DataStreamChannel channel) throws IOException { entityProducer.produce(channel); } @Override public void failed(final Exception cause) { } @Override public void releaseResources() { } }); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final Queue>> queue = new LinkedList<>(); for (int i = 0; i < REQ_NUM; i++) { final BasicHttpRequest request = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/") .build(); queue.add(streamEndpoint.execute( new BasicRequestProducer(request, AsyncEntityProducers.create("Some important message")), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null)); } while (!queue.isEmpty()) { final Future> future = queue.remove(); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response = result.getHead(); Assertions.assertNotNull(response); Assertions.assertEquals(200, response.getCode()); Assertions.assertEquals("All is well", result.getBody()); } } @Test void testExceptionInHandler() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there") { @Override protected void handle( final Message request, final AsyncServerRequestHandler.ResponseTrigger responseTrigger, final HttpContext context) throws IOException, HttpException { throw new HttpException("Boom"); } }); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final BasicHttpRequest request = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/hello") .build(); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response1 = result.getHead(); final String entity1 = result.getBody(); Assertions.assertNotNull(response1); Assertions.assertEquals(500, response1.getCode()); Assertions.assertEquals("Boom", entity1); } @Test void testMessageWithTrailers() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/hello", () -> new AbstractServerExchangeHandler>() { @Override protected AsyncRequestConsumer> supplyConsumer( final HttpRequest request, final EntityDetails entityDetails, final HttpContext context) throws HttpException { return new BasicRequestConsumer<>(entityDetails != null ? new StringAsyncEntityConsumer() : null); } @Override protected void handle( final Message requestMessage, final AsyncServerRequestHandler.ResponseTrigger responseTrigger, final HttpContext context) throws HttpException, IOException { responseTrigger.submitResponse(new BasicResponseProducer( HttpStatus.SC_OK, new DigestingEntityProducer("MD5", new StringAsyncEntityProducer("Hello back with some trailers"))), context); } }); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final BasicHttpRequest request1 = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/hello") .build(); final DigestingEntityConsumer entityConsumer = new DigestingEntityConsumer<>("MD5", new StringAsyncEntityConsumer()); final Future> future1 = streamEndpoint.execute( new BasicRequestProducer(request1, null), new BasicResponseConsumer<>(entityConsumer), null); final Message result1 = future1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result1); final HttpResponse response1 = result1.getHead(); Assertions.assertNotNull(response1); Assertions.assertEquals(200, response1.getCode()); Assertions.assertEquals("Hello back with some trailers", result1.getBody()); final List
trailers = entityConsumer.getTrailers(); Assertions.assertNotNull(trailers); Assertions.assertEquals(2, trailers.size()); final Map map = new HashMap<>(); for (final Header header: trailers) { map.put(TextUtils.toLowerCase(header.getName()), header.getValue()); } final String digest = TextUtils.toHexString(entityConsumer.getDigest()); Assertions.assertEquals("MD5", map.get("digest-algo")); Assertions.assertEquals(digest, map.get("digest")); } @Test void testNoServiceHandler() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/ehh", () -> new SingleLineResponseHandler("Hi there")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final BasicHttpRequest request = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/hello") .build(); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response1 = result.getHead(); final String entity1 = result.getBody(); Assertions.assertNotNull(response1); Assertions.assertEquals(404, response1.getCode()); Assertions.assertEquals("Resource not found", entity1); } @Test void testResponseNoContent() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there") { @Override protected void handle( final Message request, final AsyncServerRequestHandler.ResponseTrigger responseTrigger, final HttpContext context) throws IOException, HttpException { final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_NO_CONTENT); responseTrigger.submitResponse(new BasicResponseProducer(response), context); } }); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final BasicHttpRequest request = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/hello") .build(); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response1 = result.getHead(); Assertions.assertNotNull(response1); Assertions.assertEquals(204, response1.getCode()); Assertions.assertNull(result.getBody()); } @Test void testProtocolException() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/boom", () -> new AsyncServerExchangeHandler() { private final StringAsyncEntityProducer entityProducer = new StringAsyncEntityProducer("Everything is OK"); @Override public void releaseResources() { entityProducer.releaseResources(); } @Override public void handleRequest( final HttpRequest request, final EntityDetails entityDetails, final ResponseChannel responseChannel, final HttpContext context) throws HttpException, IOException { final String requestUri = request.getRequestUri(); if (requestUri.endsWith("boom")) { throw new ProtocolException("Boom!!!"); } responseChannel.sendResponse(new BasicHttpResponse(200), entityProducer, context); } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { capacityChannel.update(Integer.MAX_VALUE); } @Override public void consume(final ByteBuffer src) throws IOException { } @Override public void streamEnd(final List trailers) throws HttpException, IOException { // empty } @Override public int available() { return entityProducer.available(); } @Override public void produce(final DataStreamChannel channel) throws IOException { entityProducer.produce(channel); } @Override public void failed(final Exception cause) { releaseResources(); } }); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final BasicHttpRequest request = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/boom") .build(); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response1 = result.getHead(); final String entity1 = result.getBody(); Assertions.assertNotNull(response1); Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, response1.getCode()); Assertions.assertEquals("Boom!!!", entity1); } @Test @Disabled void testDelayedRequestSubmission() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/hello", () -> new SingleLineResponseHandler("All is well")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final Queue>> queue = new LinkedList<>(); for (int i = 0; i < REQ_NUM; i++) { final BasicHttpRequest request = BasicRequestBuilder.post() .setHttpHost(target) .setPath("/hello") .build(); final AsyncEntityProducer entityProducer = AsyncEntityProducers.create("Some important message"); queue.add(streamEndpoint.execute( new AsyncRequestProducer() { private final Random random = new Random(System.currentTimeMillis()); @Override public void sendRequest(final RequestChannel channel, final HttpContext context) throws HttpException, IOException { executorResource.getExecutorService().execute(() -> { try { Thread.sleep(random.nextInt(200)); channel.sendRequest(request, entityProducer, context); } catch (final Exception ignore) { // ignore } }); } @Override public boolean isRepeatable() { return entityProducer.isRepeatable(); } @Override public int available() { return entityProducer.available(); } @Override public void produce(final DataStreamChannel channel) throws IOException { entityProducer.produce(channel); } @Override public void failed(final Exception cause) { entityProducer.failed(cause); } @Override public void releaseResources() { entityProducer.releaseResources(); } }, new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null)); } while (!queue.isEmpty()) { final Future> future = queue.remove(); final Message result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response = result.getHead(); Assertions.assertNotNull(response); Assertions.assertEquals(200, response.getCode()); Assertions.assertEquals("All is well", result.getBody()); } } @Test void testDelayedResponseSubmission() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/hello", () -> new AbstractServerExchangeHandler>() { private final Random random = new Random(System.currentTimeMillis()); @Override protected AsyncRequestConsumer> supplyConsumer( final HttpRequest request, final EntityDetails entityDetails, final HttpContext context) throws HttpException { return new BasicRequestConsumer<>(entityDetails != null ? new StringAsyncEntityConsumer() : null); } @Override protected void handle( final Message requestMessage, final AsyncServerRequestHandler.ResponseTrigger responseTrigger, final HttpContext context) throws HttpException, IOException { executorResource.getExecutorService().execute(() -> { try { Thread.sleep(random.nextInt(200)); responseTrigger.submitResponse(AsyncResponseBuilder.create(HttpStatus.SC_OK) .setEntity(new MultiLineEntityProducer("All is well", 100)) .build(), context); Thread.sleep(random.nextInt(200)); } catch (final Exception ignore) { // ignore } }); } }); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect( "localhost", serverEndpoint.getPort(), TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final Queue>> queue = new LinkedList<>(); for (int i = 0; i < REQ_NUM; i++) { final BasicHttpRequest request = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/hello") .build(); queue.add(streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null)); } while (!queue.isEmpty()) { final Future> future = queue.remove(); final Message result = future.get(LONG_TIMEOUT.getDuration(), LONG_TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response = result.getHead(); Assertions.assertNotNull(response); Assertions.assertEquals(200, response.getCode()); MatcherAssert.assertThat(result.getBody(), Matchers.startsWith("All is well")); } } @Test void testDelayedResponseSubmissionNoResponseBody() throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/hello", () -> new AbstractServerExchangeHandler>() { private final Random random = new Random(System.currentTimeMillis()); @Override protected AsyncRequestConsumer> supplyConsumer( final HttpRequest request, final EntityDetails entityDetails, final HttpContext context) throws HttpException { return new BasicRequestConsumer<>(entityDetails != null ? new StringAsyncEntityConsumer() : null); } @Override protected void handle( final Message requestMessage, final AsyncServerRequestHandler.ResponseTrigger responseTrigger, final HttpContext context) throws HttpException, IOException { executorResource.getExecutorService().execute(() -> { try { Thread.sleep(random.nextInt(200)); responseTrigger.submitResponse(AsyncResponseBuilder.create(200) .build(), context); Thread.sleep(random.nextInt(200)); } catch (final Exception ignore) { // ignore } }); } }); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect( "localhost", serverEndpoint.getPort(), TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final Queue>> queue = new LinkedList<>(); for (int i = 0; i < REQ_NUM; i++) { final BasicHttpRequest request = BasicRequestBuilder.get() .setHttpHost(target) .setPath("/hello") .build(); queue.add(streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null)); } while (!queue.isEmpty()) { final Future> future = queue.remove(); final Message result = future.get(LONG_TIMEOUT.getDuration(), LONG_TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result); final HttpResponse response = result.getHead(); Assertions.assertNotNull(response); Assertions.assertEquals(200, response.getCode()); } } void testHeaderTooLarge(final String method) throws Exception { final HttpTestServer server = server(); final HttpTestClient client = client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); final InetSocketAddress serverEndpoint = server.start(); final HttpHost target = target(serverEndpoint); client.start(); final Future connectFuture = client.connect(target, TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); final int n = 1000; final StringBuilder buf = new StringBuilder(n); for (int i = 0; i < n; i++) { buf.append('a' + i % 10); } final String s = buf.toString(); final BasicHttpRequest request = BasicRequestBuilder.create(method) .setHttpHost(target) .setPath("/hello") .setHeader("big-f-header", s) .build(); final AsyncEntityProducer entityProducer; if (Method.POST.isSame(method)) { entityProducer = AsyncEntityProducers.create(s, ContentType.TEXT_PLAIN); } else { entityProducer = null; } final Future> future1 = streamEndpoint.execute( new BasicRequestProducer(request, entityProducer), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message result1 = future1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(result1); final HttpResponse response1 = result1.getHead(); Assertions.assertNotNull(response1); Assertions.assertEquals(431, response1.getCode()); MatcherAssert.assertThat(result1.getBody(), CoreMatchers.allOf( CoreMatchers.containsString("Maximum"), CoreMatchers.containsString("exceeded"))); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/HttpIntegrationTests.java0100644 0000000 0000000 00000007562 00000250600 030546 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import org.apache.hc.core5.http.URIScheme; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; class HttpIntegrationTests { @Nested @DisplayName("Core transport (HTTP/1.1)") class CoreTransport extends Http1CoreTransportTest { public CoreTransport() { super(URIScheme.HTTP); } } @Nested @DisplayName("Core transport (HTTP/1.1, TLS)") class CoreTransportTls extends Http1CoreTransportTest { public CoreTransportTls() { super(URIScheme.HTTPS); } } @Nested @DisplayName("Core transport (HTTP/1.1, TLSv1.3)") class CoreTransportTls13 extends Http1CoreTransportTest { public CoreTransportTls13() { super(URIScheme.HTTPS, "TLSv1.3"); } } @Nested @DisplayName("Core transport (H2)") class CoreTransportH2 extends H2CoreTransportTest { public CoreTransportH2() { super(URIScheme.HTTP); } } @Nested @DisplayName("Core transport (H2, TLS)") class CoreTransportH2Tls extends H2CoreTransportTest { public CoreTransportH2Tls() { super(URIScheme.HTTPS); } } @Nested @DisplayName("Core transport (H2, TLSv1.3)") class CoreTransportH2Tls13 extends H2CoreTransportTest { public CoreTransportH2Tls13() { super(URIScheme.HTTPS, "TLSv1.3"); } } @Nested @DisplayName("Core transport (H2, multiplexing)") class CoreTransportH2Multiplexing extends H2CoreTransportMultiplexingTest { public CoreTransportH2Multiplexing() { super(URIScheme.HTTP); } } @Nested @DisplayName("Core transport (H2, multiplexing, TLS)") class CoreTransportH2MultiplexingTls extends H2CoreTransportMultiplexingTest { public CoreTransportH2MultiplexingTls() { super(URIScheme.HTTPS); } } @Nested @DisplayName("Server filters") class HttpFilters extends AsyncServerBootstrapFilterTest { public HttpFilters() { super(); } } @Nested @DisplayName("H2 Server filters") class H2Filters extends H2ServerBootstrapFiltersTest { public H2Filters() { super(); } } @Nested @DisplayName("Authentication") class Authentication extends Http1AuthenticationTest { public Authentication() { super(false); } } @Nested @DisplayName("Authentication (immediate response)") class AuthenticationImmediateResponse extends Http1AuthenticationTest { public AuthenticationImmediateResponse() { super(true); } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/HttpProtocolTests.java0100644 0000000 0000000 00000004023 00000250600 030051 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import org.apache.hc.core5.http.URIScheme; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; class HttpProtocolTests { @Nested @DisplayName("HTTP/1.1 (plain)") class Http1 extends Http1IntegrationTest { public Http1() { super(URIScheme.HTTP); } } @Nested @DisplayName("HTTP/1.1 (TLS)") class Http1Tls extends Http1IntegrationTest { public Http1Tls() { super(URIScheme.HTTPS); } } @Nested @DisplayName("HTTP/2 (plain)") class H2 extends H2IntegrationTest { public H2() { super(URIScheme.HTTP); } } @Nested @DisplayName("HTTP/2 (TLS)") class H2Tls extends H2IntegrationTest { public H2Tls() { super(URIScheme.HTTPS); } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/MessageExchangeHandler.java0100644 0000000 0000000 00000005344 00000250600 030721 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.nio.AsyncEntityConsumer; import org.apache.hc.core5.http.nio.AsyncRequestConsumer; import org.apache.hc.core5.http.nio.support.BasicRequestConsumer; import org.apache.hc.core5.http.nio.support.AbstractServerExchangeHandler; import org.apache.hc.core5.http.protocol.HttpContext; /** * @param The message body type. * @since 5.0 */ public abstract class MessageExchangeHandler extends AbstractServerExchangeHandler> { private final AsyncRequestConsumer> requestConsumer; public MessageExchangeHandler(final AsyncRequestConsumer> requestConsumer) { super(); this.requestConsumer = requestConsumer; } public MessageExchangeHandler(final Supplier> dataConsumerSupplier) { this(new BasicRequestConsumer<>(dataConsumerSupplier)); } public MessageExchangeHandler(final AsyncEntityConsumer entityConsumer) { this(new BasicRequestConsumer<>(entityConsumer)); } @Override protected AsyncRequestConsumer> supplyConsumer( final HttpRequest request, final EntityDetails entityDetails, final HttpContext context) throws HttpException { return requestConsumer; } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/MultiBinEntityProducer.java0100644 0000000 0000000 00000005615 00000250600 031021 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import java.nio.ByteBuffer; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.nio.StreamChannel; import org.apache.hc.core5.http.nio.entity.AbstractBinAsyncEntityProducer; public class MultiBinEntityProducer extends AbstractBinAsyncEntityProducer { private final byte[] bin; private final int total; private final ByteBuffer bytebuf; private int count; public MultiBinEntityProducer(final byte[] bin, final int total, final ContentType contentType) { super(-1, contentType); this.bin = bin; this.total = total; this.bytebuf = ByteBuffer.allocate(4096); this.count = 0; } @Override public boolean isRepeatable() { return true; } @Override protected int availableData() { return Integer.MAX_VALUE; } @Override protected void produceData(final StreamChannel channel) throws IOException { while (bytebuf.remaining() > bin.length + 2 && count < total) { bytebuf.put(bin); count++; } if (bytebuf.position() > 0) { bytebuf.flip(); channel.write(bytebuf); bytebuf.compact(); } if (count >= total && bytebuf.position() == 0) { channel.endStream(); } } @Override public boolean isChunked() { return false; } @Override public long getContentLength() { return bin.length * total; } @Override public void failed(final Exception cause) { } @Override public void releaseResources() { count = 0; bytebuf.clear(); super.releaseResources(); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/MultiLineEntityProducer.java0100644 0000000 0000000 00000005750 00000250600 031200 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.nio.StreamChannel; import org.apache.hc.core5.http.nio.entity.AbstractCharAsyncEntityProducer; public class MultiLineEntityProducer extends AbstractCharAsyncEntityProducer { private final String text; private final int total; private final CharBuffer charbuf; private int count; public MultiLineEntityProducer(final String text, final int total, final Charset charset) { super(1024, -1, ContentType.TEXT_PLAIN.withCharset(charset)); this.text = text; this.total = total; this.charbuf = CharBuffer.allocate(4096); this.count = 0; } public MultiLineEntityProducer(final String text, final int total) { this(text, total, StandardCharsets.US_ASCII); } @Override public boolean isRepeatable() { return true; } @Override protected int availableData() { return Integer.MAX_VALUE; } @Override protected void produceData(final StreamChannel channel) throws IOException { while (charbuf.remaining() > text.length() + 2 && count < total) { charbuf.put(text + "\r\n"); count++; } if (charbuf.position() > 0) { charbuf.flip(); channel.write(charbuf); charbuf.compact(); } if (count >= total && charbuf.position() == 0) { channel.endStream(); } } @Override public void failed(final Exception cause) { } @Override public void releaseResources() { count = 0; charbuf.clear(); super.releaseResources(); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/MultiLineResponseHandler.java0100644 0000000 0000000 00000006345 00000250600 031315 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.nio.AsyncRequestConsumer; import org.apache.hc.core5.http.nio.AsyncServerRequestHandler; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.support.AsyncResponseBuilder; import org.apache.hc.core5.http.nio.support.BasicRequestConsumer; import org.apache.hc.core5.http.nio.support.BasicServerExchangeHandler; import org.apache.hc.core5.http.protocol.HttpContext; public class MultiLineResponseHandler extends BasicServerExchangeHandler> { public MultiLineResponseHandler(final String message, final int count) { super(new AsyncServerRequestHandler>() { @Override public AsyncRequestConsumer> prepare( final HttpRequest request, final EntityDetails entityDetails, final HttpContext context) throws HttpException { return new BasicRequestConsumer<>(entityDetails != null ? new StringAsyncEntityConsumer() : null); } @Override public void handle( final Message requestMessage, final ResponseTrigger responseTrigger, final HttpContext context) throws HttpException, IOException { responseTrigger.submitResponse( AsyncResponseBuilder.create(HttpStatus.SC_OK) .setEntity(new MultiLineEntityProducer(message, count)) .build(), context); } } ); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/NoopIOEventHandlerFactory.java0100644 0000000 0000000 00000004547 00000250600 031373 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.nio.ByteBuffer; import org.apache.hc.core5.reactor.IOEventHandler; import org.apache.hc.core5.reactor.IOEventHandlerFactory; import org.apache.hc.core5.reactor.IOSession; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.util.Timeout; class NoopIOEventHandlerFactory implements IOEventHandlerFactory { @Override public IOEventHandler createHandler(final ProtocolIOSession ioSession, final Object attachment) { return new IOEventHandler() { @Override public void connected(final IOSession session) { } @Override public void inputReady(final IOSession session, final ByteBuffer src) { } @Override public void outputReady(final IOSession session) { } @Override public void timeout(final IOSession session, final Timeout timeout) { } @Override public void exception(final IOSession session, final Exception cause) { } @Override public void disconnected(final IOSession session) { } }; } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/SingleLineResponseHandler.java0100644 0000000 0000000 00000006262 00000250600 031442 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.nio.AsyncRequestConsumer; import org.apache.hc.core5.http.nio.AsyncServerRequestHandler; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.support.AsyncResponseBuilder; import org.apache.hc.core5.http.nio.support.BasicRequestConsumer; import org.apache.hc.core5.http.nio.support.BasicServerExchangeHandler; import org.apache.hc.core5.http.protocol.HttpContext; public class SingleLineResponseHandler extends BasicServerExchangeHandler> { public SingleLineResponseHandler(final String message) { super(new AsyncServerRequestHandler>() { @Override public AsyncRequestConsumer> prepare( final HttpRequest request, final EntityDetails entityDetails, final HttpContext context) throws HttpException { return new BasicRequestConsumer<>(entityDetails != null ? new StringAsyncEntityConsumer() : null); } @Override public void handle( final Message requestMessage, final ResponseTrigger responseTrigger, final HttpContext context) throws HttpException, IOException { responseTrigger.submitResponse( AsyncResponseBuilder.create(HttpStatus.SC_OK) .setEntity(message) .build(), context); } } ); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/TLSIntegrationTest.java0100644 0000000 0000000 00000051206 00000250600 030100 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.stream.Stream; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; import org.apache.hc.core5.concurrent.BasicFuture; import org.apache.hc.core5.concurrent.CompletingFutureContribution; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.concurrent.FutureContribution; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.AsyncRequesterBootstrap; import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer; import org.apache.hc.core5.http.nio.ssl.BasicClientTlsStrategy; import org.apache.hc.core5.http.nio.ssl.BasicServerTlsStrategy; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.http.nio.ssl.TlsSupport; import org.apache.hc.core5.http.nio.ssl.TlsUpgradeCapable; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http.ssl.TLS; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.reactor.ssl.SSLBufferMode; import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer; import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier; import org.apache.hc.core5.reactor.ssl.TlsDetails; import org.apache.hc.core5.reactor.ssl.TransportSecurityLayer; import org.apache.hc.core5.ssl.SSLContexts; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.classic.LoggingConnPoolListener; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.ReflectionUtils; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.jupiter.params.provider.ValueSource; class TLSIntegrationTest { private static final Timeout TIMEOUT = Timeout.ofSeconds(30); private HttpAsyncServer server; @RegisterExtension public final AfterEachCallback serverCleanup = new AfterEachCallback() { @Override public void afterEach(final ExtensionContext context) throws Exception { if (server != null) { try { server.close(CloseMode.IMMEDIATE); } catch (final Exception ignore) { } } } }; private HttpAsyncRequester client; @RegisterExtension public final AfterEachCallback clientCleanup = new AfterEachCallback() { @Override public void afterEach(final ExtensionContext context) throws Exception { if (client != null) { try { client.close(CloseMode.GRACEFUL); } catch (final Exception ignore) { } } } }; HttpAsyncServer createServer(final TlsStrategy tlsStrategy) { return AsyncServerBootstrap.bootstrap() .setCanonicalHostName("localhost") .setIOReactorConfig( IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .setIoThreadCount(1) .build()) .setTlsStrategy(tlsStrategy) .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER) .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE) .setExceptionCallback(LoggingExceptionCallback.INSTANCE) .setIOSessionListener(LoggingIOSessionListener.INSTANCE) .setIOReactorMetricsListener(LoggingReactorMetricsListener.INSTANCE) .setRequestRouter(RequestRouter.>builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()) .create(); } HttpAsyncRequester createClient(final TlsStrategy tlsStrategy) { return AsyncRequesterBootstrap.bootstrap() .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setTlsStrategy(tlsStrategy) .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT) .setConnPoolListener(LoggingConnPoolListener.INSTANCE) .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE) .setExceptionCallback(LoggingExceptionCallback.INSTANCE) .setIOSessionListener(LoggingIOSessionListener.INSTANCE) .setIOReactorMetricsListener(LoggingReactorMetricsListener.INSTANCE) .create(); } Future executeTlsHandshake() throws Exception { final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), URIScheme.HTTPS); final ListenerEndpoint listener = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort()); final BasicFuture tlsFuture = new BasicFuture<>(null); client.connect( new HttpHost(URIScheme.HTTP.id, "localhost", address.getPort()), TIMEOUT, null, new FutureContribution(tlsFuture) { @Override public void completed(final AsyncClientEndpoint clientEndpoint) { try { ((TlsUpgradeCapable) clientEndpoint).tlsUpgrade( target, new CompletingFutureContribution<>(tlsFuture, ProtocolIOSession::getTlsDetails)); } catch (final Exception ex) { tlsFuture.failed(ex); } } }); return tlsFuture; } @ParameterizedTest(name = "TLS protocol {0}") @ArgumentsSource(SupportedTLSProtocolProvider.class) void testTLSSuccess(final TLS tlsProtocol) throws Exception { final TlsStrategy serverTlsStrategy = new TestTlsStrategy( SSLTestContexts.createServerSSLContext(), (endpoint, sslEngine) -> sslEngine.setEnabledProtocols(new String[]{tlsProtocol.getId()}), null); server = createServer(serverTlsStrategy); server.start(); final TlsStrategy clientTlsStrategy = new TestTlsStrategy(SSLTestContexts.createClientSSLContext(), (endpoint, sslEngine) -> sslEngine.setEnabledProtocols(new String[]{tlsProtocol.getId()}), null); client = createClient(clientTlsStrategy); client.start(); final Future tlsSessionFuture = executeTlsHandshake(); final TlsDetails tlsDetails = tlsSessionFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(tlsDetails); final SSLSession tlsSession = tlsDetails.getSSLSession(); final ProtocolVersion tlsVersion = TLS.parse(tlsSession.getProtocol()); MatcherAssert.assertThat(tlsVersion.greaterEquals(tlsProtocol.version), CoreMatchers.equalTo(true)); MatcherAssert.assertThat(tlsSession.getPeerPrincipal().getName(), CoreMatchers.equalTo("CN=localhost,OU=Apache HttpComponents,O=Apache Software Foundation")); } @Test void testTLSTrustFailure() throws Exception { final TlsStrategy serverTlsStrategy = new BasicServerTlsStrategy(SSLTestContexts.createServerSSLContext()); server = createServer(serverTlsStrategy); server.start(); final TlsStrategy clientTlsStrategy = new BasicClientTlsStrategy(SSLContexts.createDefault()); client = createClient(clientTlsStrategy); client.start(); final Future tlsSessionFuture = executeTlsHandshake(); final ExecutionException exception = Assertions.assertThrows(ExecutionException.class, () -> tlsSessionFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); final Throwable cause = exception.getCause(); Assertions.assertInstanceOf(SSLHandshakeException.class, cause); } @Test void testTLSClientAuthFailure() throws Exception { final TlsStrategy serverTlsStrategy = new BasicServerTlsStrategy( SSLTestContexts.createServerSSLContext(), (endpoint, sslEngine) -> sslEngine.setNeedClientAuth(true), null); server = createServer(serverTlsStrategy); server.start(); final TlsStrategy clientTlsStrategy = new BasicClientTlsStrategy(SSLTestContexts.createClientSSLContext()); client = createClient(clientTlsStrategy); client.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), URIScheme.HTTPS); final ListenerEndpoint listener = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort()); final Future> resultFuture = client.execute( new BasicRequestProducer(Method.POST, target, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final ExecutionException exception = Assertions.assertThrows(ExecutionException.class, () -> resultFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); final Throwable cause = exception.getCause(); Assertions.assertInstanceOf(IOException.class, cause); } @Test void testSSLDisabledByDefault() throws Exception { final TlsStrategy serverTlsStrategy = new TestTlsStrategy( SSLTestContexts.createServerSSLContext(), (endpoint, sslEngine) -> sslEngine.setEnabledProtocols(new String[]{"SSLv3"}), null); server = createServer(serverTlsStrategy); server.start(); final TlsStrategy clientTlsStrategy = new BasicClientTlsStrategy(SSLTestContexts.createClientSSLContext()); client = createClient(clientTlsStrategy); client.start(); final Future tlsSessionFuture = executeTlsHandshake(); Assertions.assertThrows(ExecutionException.class, () -> tlsSessionFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); } @ParameterizedTest(name = "cipher {0}") @ValueSource(strings = { "SSL_RSA_WITH_RC4_128_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_DH_anon_WITH_AES_128_CBC_SHA", "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_RSA_WITH_NULL_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", "TLS_DH_anon_WITH_AES_256_GCM_SHA384", "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_NULL_SHA256", "SSL_RSA_EXPORT_WITH_RC4_40_MD5", "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", "TLS_KRB5_EXPORT_WITH_RC4_40_SHA", "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5" }) void testWeakCipherDisabledByDefault(final String cipher) throws Exception { final TlsStrategy serverTlsStrategy = new TestTlsStrategy( SSLTestContexts.createServerSSLContext(), (endpoint, sslEngine) -> sslEngine.setEnabledCipherSuites(new String[]{cipher}), null); server = createServer(serverTlsStrategy); server.start(); final TlsStrategy clientTlsStrategy = new BasicClientTlsStrategy(SSLTestContexts.createClientSSLContext()); client = createClient(clientTlsStrategy); client.start(); final Future tlsSessionFuture = executeTlsHandshake(); Assertions.assertThrows(ExecutionException.class, () -> tlsSessionFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); } @Test void testTLSVersionMismatch() throws Exception { final TlsStrategy serverTlsStrategy = new TestTlsStrategy( SSLTestContexts.createServerSSLContext(), (endpoint, sslEngine) -> { sslEngine.setEnabledProtocols(new String[]{TLS.V_1_0.getId()}); sslEngine.setEnabledCipherSuites(new String[]{ "TLS_RSA_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_3DES_EDE_CBC_SHA"}); }, null); server = createServer(serverTlsStrategy); server.start(); final TlsStrategy clientTlsStrategy = new BasicClientTlsStrategy( SSLTestContexts.createClientSSLContext(), (endpoint, sslEngine) -> sslEngine.setEnabledProtocols(new String[]{TLS.V_1_2.getId()}), null); client = createClient(clientTlsStrategy); client.start(); final Future tlsSessionFuture = executeTlsHandshake(); final ExecutionException exception = Assertions.assertThrows(ExecutionException.class, () -> tlsSessionFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); final Throwable cause = exception.getCause(); Assertions.assertInstanceOf(IOException.class, cause); } @Test void testHostNameVerification() throws Exception { server = createServer(new BasicClientTlsStrategy(SSLTestContexts.createServerSSLContext())); server.start(); client = createClient(new BasicClientTlsStrategy(SSLTestContexts.createClientSSLContext())); client.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), URIScheme.HTTPS); final ListenerEndpoint listener = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpHost target1 = new HttpHost(URIScheme.HTTPS.id, InetAddress.getLoopbackAddress(), "localhost", address.getPort()); final Future> resultFuture1 = client.execute( target1, new BasicRequestProducer(Method.POST, target1, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(message1); Assertions.assertEquals(200, message1.getHead().getCode()); final HttpHost target2 = new HttpHost(URIScheme.HTTPS.id, InetAddress.getLoopbackAddress(), "some-other-host", address.getPort()); final Future> resultFuture2 = client.execute( target2, new BasicRequestProducer(Method.POST, target2, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final ExecutionException exception = Assertions.assertThrows(ExecutionException.class, () -> resultFuture2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); final Throwable cause = exception.getCause(); Assertions.assertInstanceOf(SSLHandshakeException.class, cause); } static class SupportedTLSProtocolProvider implements ArgumentsProvider { int javaVere = ReflectionUtils.determineJRELevel(); @Override public Stream provideArguments(final ExtensionContext context) { if (javaVere >= 11) { return Stream.of(Arguments.of(TLS.V_1_2), Arguments.of(TLS.V_1_3)); } else { return Stream.of(Arguments.of(TLS.V_1_2)); } } } static class TestTlsStrategy implements TlsStrategy { private final SSLContext sslContext; private final SSLSessionInitializer initializer; private final SSLSessionVerifier verifier; public TestTlsStrategy( final SSLContext sslContext, final SSLSessionInitializer initializer, final SSLSessionVerifier verifier) { this.sslContext = Args.notNull(sslContext, "SSL context"); this.initializer = initializer; this.verifier = verifier; } @Override public void upgrade( final TransportSecurityLayer tlsSession, final NamedEndpoint endpoint, final Object attachment, final Timeout handshakeTimeout, final FutureCallback callback) { tlsSession.startTls(sslContext, endpoint, SSLBufferMode.STATIC, TlsSupport.enforceStrongSecurity(initializer), verifier, handshakeTimeout, callback); } /** * @deprecated do not use. */ @Deprecated @Override public boolean upgrade( final TransportSecurityLayer tlsSession, final HttpHost host, final SocketAddress localAddress, final SocketAddress remoteAddress, final Object attachment, final Timeout handshakeTimeout) { tlsSession.startTls(sslContext, host, SSLBufferMode.STATIC, TlsSupport.enforceStrongSecurity(initializer), verifier, handshakeTimeout, null); return true; } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/TLSUpgradeTest.java0100644 0000000 0000000 00000016732 00000250600 027211 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import static org.hamcrest.MatcherAssert.assertThat; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.concurrent.Future; import org.apache.hc.core5.concurrent.BasicFuture; import org.apache.hc.core5.concurrent.CompletingFutureContribution; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer; import org.apache.hc.core5.http.nio.ssl.TlsUpgradeCapable; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy; import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.reactor.ProtocolIOSession; import org.apache.hc.core5.reactor.ssl.TlsDetails; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.extension.nio.HttpAsyncRequesterResource; import org.apache.hc.core5.testing.extension.nio.HttpAsyncServerResource; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; class TLSUpgradeTest { private static final Timeout TIMEOUT = Timeout.ofSeconds(30); @RegisterExtension private final HttpAsyncServerResource serverResource; @RegisterExtension private final HttpAsyncRequesterResource clientResource; public TLSUpgradeTest() { this.serverResource = new HttpAsyncServerResource(); this.serverResource.configure(bootstrap -> bootstrap .setTlsStrategy(new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext())) .setIOReactorConfig( IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) .setRequestRouter(RequestRouter.>builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()) ); this.clientResource = new HttpAsyncRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext())) .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) ); } @Test void testTLSUpgrade() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), URIScheme.HTTPS); final ListenerEndpoint listener = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); final HttpAsyncRequester requester = clientResource.start(); final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort()); final Future> resultFuture1 = requester.execute( new BasicRequestProducer(Method.POST, target, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null); final Message message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message1, CoreMatchers.notNullValue()); final HttpResponse response1 = message1.getHead(); assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body1 = message1.getBody(); assertThat(body1, CoreMatchers.equalTo("some stuff")); // Connect using plain HTTP scheme final Future endpointFuture = requester.connect( new HttpHost(URIScheme.HTTP.id, "localhost", address.getPort()), TIMEOUT); final AsyncClientEndpoint clientEndpoint = endpointFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertInstanceOf(TlsUpgradeCapable.class, clientEndpoint); // Upgrade to TLS final BasicFuture tlsFuture = new BasicFuture<>(null); ((TlsUpgradeCapable) clientEndpoint).tlsUpgrade(target, new CompletingFutureContribution<>(tlsFuture, ProtocolIOSession::getTlsDetails)); final TlsDetails tlsDetails = tlsFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); Assertions.assertNotNull(tlsDetails); // Execute request over HTTPS final Future> resultFuture2 = clientEndpoint.execute( new BasicRequestProducer(Method.POST, target, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null); final Message message2 = resultFuture2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); assertThat(message2, CoreMatchers.notNullValue()); final HttpResponse response2 = message2.getHead(); assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK)); final String body2 = message2.getBody(); assertThat(body2, CoreMatchers.equalTo("some stuff")); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/TestDefaultListeningIOReactor.java0100644 0000000 0000000 00000010723 00000250600 032242 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.nio; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.DefaultListeningIOReactor; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.IOReactorStatus; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.util.TimeValue; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** * Basic tests for {@link DefaultListeningIOReactor}. */ class TestDefaultListeningIOReactor { private DefaultListeningIOReactor ioReactor; @BeforeEach void setup() { final IOReactorConfig reactorConfig = IOReactorConfig.custom() .setIoThreadCount(1) .build(); this.ioReactor = new DefaultListeningIOReactor(new NoopIOEventHandlerFactory(), reactorConfig, null); } @AfterEach void cleanup() { if (this.ioReactor != null) { this.ioReactor.close(CloseMode.IMMEDIATE); } } @Test void testEndpointUpAndDown() throws Exception { ioReactor.start(); Set endpoints = ioReactor.getEndpoints(); Assertions.assertNotNull(endpoints); Assertions.assertEquals(0, endpoints.size()); final Future future1 = ioReactor.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); final ListenerEndpoint endpoint1 = future1.get(); final Future future2 = ioReactor.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); final ListenerEndpoint endpoint2 = future2.get(); final int port = ((InetSocketAddress) endpoint2.getAddress()).getPort(); endpoints = ioReactor.getEndpoints(); Assertions.assertNotNull(endpoints); Assertions.assertEquals(2, endpoints.size()); endpoint1.close(); endpoints = ioReactor.getEndpoints(); Assertions.assertNotNull(endpoints); Assertions.assertEquals(1, endpoints.size()); final ListenerEndpoint endpoint = endpoints.iterator().next(); Assertions.assertEquals(port, ((InetSocketAddress) endpoint.getAddress()).getPort()); ioReactor.close(CloseMode.GRACEFUL); ioReactor.awaitShutdown(TimeValue.ofSeconds(5)); Assertions.assertEquals(IOReactorStatus.SHUT_DOWN, ioReactor.getStatus()); } @Test void testEndpointAlreadyBound() throws Exception { ioReactor.start(); final Future future1 = ioReactor.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); final ListenerEndpoint endpoint1 = future1.get(); final int port = ((InetSocketAddress) endpoint1.getAddress()).getPort(); final Future future2 = ioReactor.listen(new InetSocketAddress(port)); Assertions.assertThrows(ExecutionException.class, () -> future2.get()); ioReactor.close(CloseMode.GRACEFUL); ioReactor.awaitShutdown(TimeValue.ofSeconds(5)); Assertions.assertEquals(IOReactorStatus.SHUT_DOWN, ioReactor.getStatus()); } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/reactive/0040755 0000000 0000000 00000000000 00000250600 024543 5ustar000000000 0000000 httpcore5-testing/src/test/java/org/apache/hc/core5/testing/reactive/IntegrationTests.java0100644 0000000 0000000 00000003442 00000250600 030714 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.reactive; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; class IntegrationTests { @Nested @DisplayName("Reactive client (HTTP/1.1)") class CoreFunctions extends ReactiveClientTest { public CoreFunctions() { super(HttpVersionPolicy.FORCE_HTTP_1); } } @Nested @DisplayName("Reactive client (HTTP/2)") class CoreFunctionsTls extends ReactiveClientTest { public CoreFunctionsTls() { super(HttpVersionPolicy.FORCE_HTTP_2); } } } httpcore5-testing/src/test/java/org/apache/hc/core5/testing/reactive/ReactiveClientTest.java0100644 0000000 0000000 00000036252 00000250600 031154 0ustar000000000 0000000 /* * ==================================================================== * 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.apache.hc.core5.testing.reactive; import static java.lang.String.format; import static org.hamcrest.MatcherAssert.assertThat; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketTimeoutException; import java.net.URI; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.WritableByteChannel; import java.util.List; import java.util.Random; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Observable; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStreamResetException; import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester; import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.impl.routing.RequestRouter; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy; import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy; import org.apache.hc.core5.reactive.ReactiveEntityProducer; import org.apache.hc.core5.reactive.ReactiveResponseConsumer; import org.apache.hc.core5.reactive.ReactiveServerExchangeHandler; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.testing.SSLTestContexts; import org.apache.hc.core5.testing.extension.nio.H2AsyncRequesterResource; import org.apache.hc.core5.testing.extension.nio.H2AsyncServerResource; import org.apache.hc.core5.testing.reactive.Reactive3TestUtils.StreamDescription; import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.reactivestreams.Publisher; abstract class ReactiveClientTest { private static final Timeout SOCKET_TIMEOUT = Timeout.ofSeconds(30); private static final Timeout RESULT_TIMEOUT = Timeout.ofSeconds(60); private static final Random RANDOM = new Random(); private final HttpVersionPolicy versionPolicy; @RegisterExtension private final H2AsyncServerResource serverResource; @RegisterExtension private final H2AsyncRequesterResource clientResource; public ReactiveClientTest(final HttpVersionPolicy httpVersionPolicy) { this.versionPolicy = httpVersionPolicy; this.serverResource = new H2AsyncServerResource(); this.serverResource.configure(bootstrap -> bootstrap .setVersionPolicy(versionPolicy) .setTlsStrategy(new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext())) .setIOReactorConfig( IOReactorConfig.custom() .setSoTimeout(SOCKET_TIMEOUT) .build()) .setRequestRouter(RequestRouter.>builder() .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new ReactiveServerExchangeHandler(new ReactiveEchoProcessor())) .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) .build()) ); this.clientResource = new H2AsyncRequesterResource(); this.clientResource.configure(bootstrap -> bootstrap .setVersionPolicy(versionPolicy) .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext())) .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(SOCKET_TIMEOUT) .build()) ); } @Test void testSimpleRequest() throws Exception { final InetSocketAddress address = startServer(); final HttpAsyncRequester requester = clientResource.start(); final byte[] input = new byte[1024]; RANDOM.nextBytes(input); final Publisher publisher = Flowable.just(ByteBuffer.wrap(input)); final ReactiveEntityProducer producer = new ReactiveEntityProducer(publisher, input.length, null, null); final BasicRequestProducer request = getRequestProducer(address, producer); final ReactiveResponseConsumer consumer = new ReactiveResponseConsumer(); requester.execute(request, consumer, SOCKET_TIMEOUT, null); final Message> response = consumer.getResponseFuture() .get(RESULT_TIMEOUT.getDuration(), RESULT_TIMEOUT.getTimeUnit()); final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); final WritableByteChannel writableByteChannel = Channels.newChannel(byteArrayOutputStream); for (final ByteBuffer byteBuffer : Observable.fromPublisher(response.getBody()).toList().blockingGet()) { writableByteChannel.write(byteBuffer); } writableByteChannel.close(); final byte[] output = byteArrayOutputStream.toByteArray(); Assertions.assertArrayEquals(input, output); } private BasicRequestProducer getRequestProducer(final InetSocketAddress address, final ReactiveEntityProducer producer) { return new BasicRequestProducer(Method.POST, URI.create("http://localhost:" + address.getPort()), producer); } @Test void testLongRunningRequest() throws Exception { final InetSocketAddress address = startServer(); final HttpAsyncRequester requester = clientResource.start(); final long expectedLength = 6_554_200L; final AtomicReference expectedHash = new AtomicReference<>(); final Flowable stream = Reactive3TestUtils.produceStream(expectedLength, expectedHash); final ReactiveEntityProducer producer = new ReactiveEntityProducer(stream, -1, null, null); final BasicRequestProducer request = getRequestProducer(address, producer); final ReactiveResponseConsumer consumer = new ReactiveResponseConsumer(); requester.execute(request, consumer, SOCKET_TIMEOUT, null); final Message> response = consumer.getResponseFuture() .get(RESULT_TIMEOUT.getDuration(), RESULT_TIMEOUT.getTimeUnit()); final StreamDescription desc = Reactive3TestUtils.consumeStream(response.getBody()).blockingGet(); Assertions.assertEquals(expectedLength, desc.length); Assertions.assertEquals(expectedHash.get(), TextUtils.toHexString(desc.md.digest())); } @Test void testManySmallBuffers() throws Exception { // This test is not flaky. If it starts randomly failing, then there is a problem with how // ReactiveDataConsumer signals capacity with its capacity channel. The situations in which // this kind of bug manifests depend on the ordering of several events on different threads // so it's unlikely to consistently occur. final InetSocketAddress address = startServer(); final HttpAsyncRequester requester = clientResource.start(); for (int i = 0; i < 10; i++) { final long expectedLength = 1_024_000; final int maximumBlockSize = 1024; final AtomicReference expectedHash = new AtomicReference<>(); final Publisher stream = Reactive3TestUtils.produceStream(expectedLength, maximumBlockSize, expectedHash); final ReactiveEntityProducer producer = new ReactiveEntityProducer(stream, -1, null, null); final BasicRequestProducer request = getRequestProducer(address, producer); final ReactiveResponseConsumer consumer = new ReactiveResponseConsumer(); requester.execute(request, consumer, SOCKET_TIMEOUT, null); final Message> response = consumer.getResponseFuture() .get(RESULT_TIMEOUT.getDuration(), RESULT_TIMEOUT.getTimeUnit()); final StreamDescription desc = Reactive3TestUtils.consumeStream(response.getBody()).blockingGet(); Assertions.assertEquals(expectedLength, desc.length); Assertions.assertEquals(expectedHash.get(), TextUtils.toHexString(desc.md.digest())); } } @Test void testRequestError() throws Exception { final InetSocketAddress address = startServer(); final HttpAsyncRequester requester = clientResource.start(); final RuntimeException exceptionThrown = new RuntimeException("Test"); final Publisher publisher = Flowable.error(exceptionThrown); final ReactiveEntityProducer producer = new ReactiveEntityProducer(publisher, 100, null, null); final BasicRequestProducer request = getRequestProducer(address, producer); final ReactiveResponseConsumer consumer = new ReactiveResponseConsumer(); final Future future = requester.execute(request, consumer, SOCKET_TIMEOUT, null); final ExecutionException exception = Assertions.assertThrows(ExecutionException.class, () -> future.get(RESULT_TIMEOUT.getDuration(), RESULT_TIMEOUT.getTimeUnit())); Assertions.assertTrue(exception.getCause() instanceof HttpStreamResetException); Assertions.assertSame(exceptionThrown, exception.getCause().getCause()); } @Test void testRequestTimeout() throws Exception { final InetSocketAddress address = startServer(); final HttpAsyncRequester requester = clientResource.start(); final AtomicBoolean requestPublisherWasCancelled = new AtomicBoolean(); final Publisher publisher = Flowable.never() .doOnCancel(() -> requestPublisherWasCancelled.set(true)); final ReactiveEntityProducer producer = new ReactiveEntityProducer(publisher, -1, null, null); final BasicRequestProducer request = getRequestProducer(address, producer); final ReactiveResponseConsumer consumer = new ReactiveResponseConsumer(); final Future future = requester.execute(request, consumer, Timeout.ofSeconds(1), null); final ExecutionException exception = Assertions.assertThrows(ExecutionException.class, () -> future.get(RESULT_TIMEOUT.getDuration(), RESULT_TIMEOUT.getTimeUnit())); Assertions.assertTrue(requestPublisherWasCancelled.get()); final Throwable cause = exception.getCause(); if (versionPolicy == HttpVersionPolicy.FORCE_HTTP_1) { Assertions.assertTrue(cause instanceof SocketTimeoutException, "Expected SocketTimeoutException, but got " + cause.getClass().getName()); } else if (versionPolicy == HttpVersionPolicy.FORCE_HTTP_2) { Assertions.assertTrue(cause instanceof HttpStreamResetException, format("Expected RST_STREAM, but %s was thrown", cause.getClass().getName())); } else { Assertions.fail("Unknown HttpVersionPolicy: " + versionPolicy); } } @Test @Disabled("Fails intermittently in GitHub Actions") void testResponseCancellation() throws Exception { final InetSocketAddress address = startServer(); final HttpAsyncRequester requester = clientResource.start(); final AtomicBoolean requestPublisherWasCancelled = new AtomicBoolean(); final AtomicReference requestStreamError = new AtomicReference<>(); final Publisher stream = Reactive3TestUtils.produceStream(Long.MAX_VALUE, 1024, null) .doOnCancel(() -> requestPublisherWasCancelled.set(true)) .doOnError(requestStreamError::set); final ReactiveEntityProducer producer = new ReactiveEntityProducer(stream, -1, null, null); final BasicRequestProducer request = getRequestProducer(address, producer); final ReactiveResponseConsumer consumer = new ReactiveResponseConsumer(); final Future future = requester.execute(request, consumer, SOCKET_TIMEOUT, null); final Message> response = consumer.getResponseFuture() .get(RESULT_TIMEOUT.getDuration(), RESULT_TIMEOUT.getTimeUnit()); final AtomicBoolean responsePublisherWasCancelled = new AtomicBoolean(); final List outputBuffers = Flowable.fromPublisher(response.getBody()) .doOnCancel(() -> responsePublisherWasCancelled.set(true)) .take(3) .toList() .blockingGet(); Assertions.assertEquals(3, outputBuffers.size()); Assertions.assertTrue(responsePublisherWasCancelled.get(), "The response subscription should have been cancelled"); final Exception exception = Assertions.assertThrows(Exception.class, () -> future.get(RESULT_TIMEOUT.getDuration(), RESULT_TIMEOUT.getTimeUnit())); assertThat(exception, CoreMatchers.anyOf( CoreMatchers.instanceOf(CancellationException.class), CoreMatchers.instanceOf(ExecutionException.class))); Assertions.assertTrue(exception.getCause() instanceof HttpStreamResetException); Assertions.assertTrue(requestPublisherWasCancelled.get()); Assertions.assertNull(requestStreamError.get()); } private InetSocketAddress startServer() throws IOException, InterruptedException, ExecutionException { final HttpAsyncServer server = serverResource.start(); final ListenerEndpoint listener = server.listen(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), URIScheme.HTTP).get(); final InetSocketAddress address = (InetSocketAddress) listener.getAddress(); return address; } } httpcore5-testing/src/test/resources/0040755 0000000 0000000 00000000000 00000250600 016736 5ustar000000000 0000000 httpcore5-testing/src/test/resources/log4j2-debug.xml.template0100644 0000000 0000000 00000003125 00000250600 023455 0ustar000000000 0000000 httpcore5-testing/src/test/resources/log4j2.xml0100644 0000000 0000000 00000002306 00000250600 020557 0ustar000000000 0000000 httpcore5-testing/src/test/resources/test-ca.jks0100644 0000000 0000000 00000002015 00000250600 021002 0ustar000000000 0000000 test-caX.50900  0  *H  0z1#0!U Apache Software Foundation10U HttpComponents Project10U Test CA1 0 *H  dev@hc.apache.org0  141013150120Z22880728150120Z0z1#0!U Apache Software Foundation10U HttpComponents Project10U Test CA1 0 *H  dev@hc.apache.org0"0  *H 0 xGo#3^dUp!HFogKQN<4S ^fƚ÷lBMt #cBX0ty (gY-nE+ƷoFo_0`8"8o^쩞 Պ]{%OZOK浥KhXwX dS= 8@]9~9Nolk16gDe9jQ2]rlfz⢴:0i t<P0N0Udv)/K0U#0dv)/K0 U00  *H  Q)&X:)e[]t_ gA4߼lWSbCVLU+) ,P/}h IVf7c<~W>s7KE CTkZN*$끾,?9㵵K`CXQ5Iᇿ #b:Rl #/!!lHikۻ{ܚߔ͗.fD:[zdr9|2(.!JHhttpcore5-testing/src/test/resources/test-client.p120100644 0000000 0000000 00000021215 00000250600 021513 0ustar000000000 0000000 0"0"B *H "3"/0"+0  *H   0 0P *H  00) *H  0/}Հz]O{FP3Wg. AdJHGM[ζ-He/ѵ͜tb e h8+E2ް=^EDyRL^9w}&oN'+ᾠ+gCBś#>@b &DsUax~m0&R.2xÇi08h@[n0̀b N=\#D1J yV kMLR#rwm$BΦUݦ`]"Ē*\y6w(Q":ظhs Ԫ!=Dc1B0 *H  1client10! *H  1Time 15708095392920P *H  00) *H  0N7o35W4LP8w9c(t&_&3l/E'h'şQ~3ҬTDkf"I{g4&Ehdĸ pz >#py~.Yz5rH,%J}SP)^༶Q5MvpVpbʮ|t@ieC]>:8Tvr{69ܸ9c5!ܝz |!Hr5S2A"G΄-nq^XEKnC! YWJCӖf2KA!(@T'](> Ը v/c{ a pLuD10KBeFbaڏ 5L m1.xL>(%;iBc=Z-IJ_c*B XA2N9ͩԃ}–`[Ba!h7lZPe3%\,<સ`/G 'BWMSٱCF|98@~sYW?1˴3r@_E{U'7eQg+aClK8v XIpp[R-2J9,T{H+A M&F tR頨&$*;2K؏< qRmIDWg,"ku4ĀyFHh-f?`3@#W`F1IrE޽{xb9v8Pa~G{VivhxM.˜iy^W6wȽatf c[WS@<:GdMA/VO\A5SI5S [Z הH&.s:?}+{8 /Mr–]&Nl@ݱ]LNio2;jxǂOјg;e" uRh7Ht{ᗵMMSXR:GVmMV܏ͻfɍ,e*+1`JTI]ߗ__yw (5h/f2B 47"hi)r.~S?v[-Wӊpt4kfW r*P(j޺P*AY޳dFy}!֏ҙuxjܰa[O^fcnpwG0Uì1B0 *H  1client20! *H  1Time 15708095451480d *H U0Q0J *H 0) *H  0xykʡR^(4XoEPVtn]2%IAK` \ZeV˞WAY =}(.&^)؍`>7*ŎM sH=>%='Vz<1b<|P p6w$ 0a̮p.4x&H&Y/1͎ۏe_5c0eSs'۷"__L1LbtaS'KpVB:PZ{^ /KsleY0#q09 !F.F5t+o09.4`I$Ђ qۭ ĄGtnؠ ʅS߹:*RLͺ EXp8_W8q_b-a&ru):Jn)E{9s4{+)-5rLL<.`O䰘&<ӳܸv,Ҝ(PN}&=b5w~NBCwrI,Lۦf`Rۙ=|̊Sá {d@HӝןM'w7%~K t\/N.:$z]/3&mTunnŁ#f84 O]'J^L;ֲx!M!;Gå#?!Ǹ֯'ΆUBpxm"MfhZI12S MDrt?]ujP(E䛇&$\/+M4S4Pط^VsJO:KH<4OYskêdӑ3OPJg8 IA+0uix=^*e!PCmDФ9&ʰITc|/3+~gD3~}6ƀ }d-7K15_Ri:1z*oFx>\?ڱxEء{|BD(WPLS`g}QNH52$AOs")[|_ngq3%لWD JцbiTɇDezOӋWa"Ƀ 6~H,f](nvG¸+|hW"qR({|us’Z4U $0; :}m0pJx (ߠɽn*{btrο";MA┟kyf*aRT XOUS~[Mwsr xGp}N((1}a\8ټcBӁ9v&*X.14қ`8L˲Hlǚ,y$UR?ԋڧ[t3NaX2l(Ø%—m|fs/$>m\$;Lb쫯&b5l?{1h29Aq~W6CG:]D`N";Z_(4[Tې_"LP/K,g&,x@OP{ <{KPT~+1L ʑ!&U!U &ߗg]@-b<4/3d`W:3B|`1ձ}>0ꗧ~&ƌz v5nzo[s];tV應Ѻ1hor2AR48Xb҈lW1!{膩$O Y|g9 +U6Gc\} sN~v/$Le^ߪN- CŔD1i\vPg z4b \]NS=MwIKZKv $M1bPZdztlEگϲEF'5U}Ѻi'oM47Az.' zo%9^I+nJz}%h7MߵHvˋgTě#+|Uѥo֞ [ͭeY 2B%slYSߝИ"0b'04̂u9`39F' ,/. c.0PuhOJRԧۯ+=s ",nbYdcnt{%?d)Z(ް +›mbMXyQ|bLmTTKgpiĖH _?PG6R'iGBzoCvU oK\]%.)u :x?J-~ǡYKJRIZT=w]tΤs;~BspmH)RRe'Q0~dr&Cjz^gڟ?d,_'yh~ne^5!o9yIO@:vmuRYZ);&&\H^σMɛc}G EݖPOiimԟ;%kлf J#P٦OOğ\B61&zZс*m|wtiԂ0BXTŲV歱3TF}vx9̷BlFL,ZG7"[!ªn\lc,kgْdMPmv Z(>hO)"I܈vQsͫ1^c8"GwǮJpRxo֟۞Z- oE?KvTu]@;gJ\ꑙ.#ԻnY'P_9AڲA !9$cqτbK@3Uk]@c+缶 W1CbXTu~ fPR afT~R:bi} RVgtAط98Jo:QvwUV45{ h/V *ٍjO2?>U9t/-&P0\3`Ǎc Y09Hwj`f񀫪b[L_ DKȸA4}ay)?1ϴ?L:sK.4՟Z2P#A WoEPJ9b]mal˝H3 .]ktcKc׬Iy풌l0к68^HWH|`v3 vA$;DJ4@Q4~mxcM;|A Ӟdzd|y&Yܮ\+HFO᭧p| c$kpo2g  M$ӶRϓOWu`^zIA[-sw ЃnA>Qծa朦::S U+.{NePa@/P" /H<[:ފJ^^>'q3Ny ?Ir\+eE^59p)[=kqYglMP9wP-ǁui虱p$D>O$j۩ M jZݯ/,2VlGߺ7.S@.*pi?ȸbv۲uo Ls~Z|7Y#ڹ&z-S?K1ITUEìIZ+S.|fYkLO;_vTv'͟(,%kP])fSh=T%@cZ6ʳhRr\B= xX)Q`o-C,qCL`8I"yDyU\J_tD{Q]54w6rNn_MYnߙ-01l0>0!0 +4}s?2!3M:VH5ֵhttpcore5-testing/src/test/resources/test-server.p120100644 0000000 0000000 00000011717 00000250600 021551 0ustar000000000 0000000 00 *H uq0m0i *H ZV0R0N *H  00) *H  0Mi"V/ƉPju)f)0t~{Y|xJa7A[zJ(6st_^C 6/ApLJcG jOa Vzص3w|Wu7Xz0M~G7#{/Lp[tK_ V%Kb.E݉o;6Z*9 bYeZ.&ύ).*tXa,7ږ] ridHy4eMYaQ?>Vf> "nLu\}r!7Cm$d˰\\hѴKXZFQ3SR٣˜-!9p/.~mT7iLX-cI8-=5ʲ鈺5aeܹyIBTJyMOYѿ k)1`AfDGC1IgT5WdoWdboj& ;ϨE#Ra#V{_!1h\1݁79M"PG*%Q;_>5JQLÔȐh>\@?[NJ04R~ivmʷZ}Ŋ7sHtsMK'ge;e' c}>zC([bdu$ONjԫ8k-t3ڡUC6>|]l!#ݜMԸ>͞%Ty4Eg\s3j#;( ?96F-f<)w ilr^`: /{"zQN6:ƭ1@0 *H  1 server0! *H  1Time 15708094235680  *H  0 0  *H 0) *H  0U* nJؼ,+#P zI%?$Rx{Rnʔ^DĠW/Զ~s&"[Ofslc[#^,"*5T~sd w|E$QLߔ4hg4XߒQпp{EQ2UR*x2IwsOF@3Q}DD:?<^ 6aV '$~2+;:4;^XjK.2~uۻє6K붴IC"iKŕ*`!Q,\|&w3/SlS;_Dq7敀ls3/q*H>4 @'_;nc!;W6¿,Vps"r:'\k$ay3g%&!Ho;z_i[kCO|C9Qui@?tk=QT扽`3c FNHcD+g8iݕĮ`cز: _8et QG٣~ᙼ%\k3h'zqgY:s FX48Ujָg/Q8 :&B=d 704#-$E-v"rU Ž,mD_;6d)hN9hrKt^|] @Z [~`gΕă@|q/Vfg:)gC9SkmZXE:j ؚGo7 p4fk#Oļfcb ifit@7:/i(~`ğ^rhOB(DNvpm%xݯA%V+?+ yX*r^ TѠ vJek,g`u֜/} % qdtAB6#L2ΎeJaǫF쁊Fgo Vz!@4j-:85 c۷`t$7pcNo b9A)@*2w/]Tͮp=&=QHWegV#2'\4AcK k.ʦ] .l5wF9$!pN!Vu9αy$=%uzA NtK^Qq mc,5,怘jNѓ0-*7)X=~DjTmrQC8Y h]~st\.s9 ѷ|V~9M`yߏ]ú[=dI 5>G8ȺI0_S WW:Enc" uklmXMgc18 XrL)8p(Q&p?$!p?=lƧdrfV$ >Xʿ?CZx̿fv`z/aTE{vJJkZ`Դzb9-qܖ?{v?;SUɾ]BGk~9b@O>]c( }PKf)9qN<$ Cf[dWR`X|,yp}e$w|ʕ~H,mg0 D\=,ho:M<p U-I%NJcܙ1`>2iH{f_9 IQZ)C4(”DO6npє)6SfQr$ztmẵ2b&cO餡&j/npb& ^1 yXjnU"?,!? &lS/9~+u6 .ThiͬpiιC|f~GW ESf su 碞^OA >qC{8詯'YEṟXZ+ϋ1OJFdi%^çtQ㽮ztrQ@3YU8ܺq9C,AYĩ-)*bRO&վd ac&"XeF=m` |? `hp,w_~'$51vWf/ xTy:>FQE:n̈́~)yIvY]~,I;ٴm5M3<6S!qkegjqmsX_G}Gy USW n-y(E[eHEtD oGptC?W4IP|B&,OXVM3e||8I5fV/kuĊd$w.؍fXNο7G%(ja_pPKRm_!K"^ h~>[}`WjwХP2!wTv!)JgВd~q6q^JB>h-Ͷhng޹&~O=j:EA85KoU|4[f'm./Hkul @eB 7ox'ϙm\3!L&F^(2{Q|Dgk-uVSeܞE{?l- iP1ߘM\-ty0qCS #vFCn?\f8͇ ^m XeW#qv߇`XdPz"2tXlGB/W=`թ"BCTѯ{4 * I欯=HW[0_Vg{4]4SŒToYQ r^:^=XQKFAC.2` &H wj<` ݲ~K8b<1oe\"5me~LчUb.4&+TօMݼƀ5Կc[+UV20b Y0>0!0 + (5df|N(A?` ]dhttpcore5-testing/src/test/resources/test.p120100644 0000000 0000000 00000005077 00000250600 020247 0ustar000000000 0000000 0 ;0  *H   0 0 *H rn0j0f *H  00) *H  0`4ALI2vP)Thʡ{c'$jTkN|Gh^o#qA+kw|]"ơQ`'^Ja#6"y?1CR CS!Hn _77hJ_G%(`GI?C?¤GS<#9w?_&TXf(qKW9Z~ppy0Sa3/Jnj;QxcKъ^Lbӂ @[\Q"+˻2lt^j~qNSz24T_ )f"wW-P>bȻ`&., tq[S_ԇڍ1E+]Զ]LtdoVZf-@|`T#|nf:D16[y^|`qVC^OV(dQ]gh8!ɊcNCSһK*&'G# }xPc;cv9jT/`x3JG'gO`$γE(lp`Kߡl^ظ:Ⲑ%~7C ^#/oF] :6E//zcBkViEd"vb$)|UF͗%# "ASEȵ24`t: jFrٞ@ D0|aANvlWzz'g]v~&)]cWlCǼ Ū?GdDdɑR8ڄX=/ xpS1ܢN%ߋх"7'o#U^)em:YwfH'ӽ3 !<PNh >\/#+K -{;h+Gʈnם6U<\7-눌C$gJ7wطi??WF)l9οpo}\CAz1 ZW-f!ʰbT 2-=$@8͚#h,Æ:W:6zZDJ$3/FF\&S /+`QU A197 l4e.54{wBJi,1X03 *H  1&$simple-http-server0! *H  1Time 15708083005120T *H E0A0: *H 0) *H  0:Cztr;!K ,Pe4-3m!DwހZBݪa5W ݲF;%C 0F}B2g.2S)V㊲_m qWQ+`cf0Y{ <ӌzDt@lO& CNJ) Ssʗh*R9iQ ]3kZ 7(Kl#GCc/q*~aYHLy+Z5Bpn\-/~cQ5ڴ9IcZ#6EғB&_t3IӼTP,J[ D0c`t2;5F4$uERui&tﳹ74<~jTmseѵipi7 zvc}Q!SrZKgA}j~`dxzMXr ҷ> |N.d .sc?9>1ԛÃmG}[@'v&X)"S@q>dzD 1x$$R{ꯀFt4hApf>r.|KcJ;r 0t!Vtip ѤK/n)( Eiv*0T9BѦ}QlPNKjlYBM y!|ҰL'CSi 0!0 +Hb;`wA|˪,q41xcPdBOڛ2httpcore5-testing/src/test/resources/docker/0040755 0000000 0000000 00000000000 00000250600 020205 5ustar000000000 0000000 httpcore5-testing/src/test/resources/docker/BUILDING.txt0100644 0000000 0000000 00000004024 00000250600 022140 0ustar000000000 0000000 = SSL key / cert material Execute in the project root # Issue a certificate request --- openssl req -config test-CA/openssl.cnf -new -nodes -sha256 -days 36500 \ -subj '/O=Apache Software Foundation/OU=HttpComponents Project/CN=localhost/emailAddress=dev@hc.apache.org/' \ -addext 'subjectAltName = DNS:localhost' \ -keyout httpcore5-testing/src/test/resources/docker/server-key.pem \ -out httpcore5-testing/src/test/resources/docker/server-certreq.pem --- # Verify the request --- openssl req -in httpcore5-testing/src/test/resources/docker/server-certreq.pem -text -noout --- # Sign new certificate with the test CA key --- openssl ca -config test-CA/openssl.cnf -days 36500 \ -out httpcore5-testing/src/test/resources/docker/server-cert.pem \ -in httpcore5-testing/src/test/resources/docker/server-certreq.pem \ && rm httpcore5-testing/src/test/resources/docker/server-certreq.pem --- # Export the certificate and the key into P12 store --- openssl pkcs12 -export -out httpcore5-testing/src/test/resources/docker/server.p12 \ -CAfile test-CA/ca-cert.pem \ -in httpcore5-testing/src/test/resources/docker/server-cert.pem \ -inkey httpcore5-testing/src/test/resources/docker/server-key.pem \ -passin pass:nopassword -passout pass:nopassword --- # Verify the P12 store --- openssl pkcs12 -info -in httpcore5-testing/src/test/resources/docker/server.p12 \ -passin pass:nopassword -passout pass:nopassword --- --- keytool -list -keystore httpcore5-testing/src/test/resources/docker/server.p12 -storepass nopassword --- # Create JKS store with the Test CA cert --- keytool -import -trustcacerts -alias test-ca -file test-CA/ca-cert.pem -keystore httpcore5-testing/src/test/resources/test-ca.jks -storepass nopassword --- = Running a local version of HttpBin # Create container --- docker container create -it --name test-httpbin -p 8080:80 kennethreitz/httpbin:latest docker start test-httpbin docker logs test-httpbin --- # Trouble-shoot container --- docker exec -it test-httpbin bash --- # Delete container --- docker rm --force test-httpbin ---httpcore5-testing/src/test/resources/docker/server-cert.pem0100644 0000000 0000000 00000011171 00000250600 023147 0ustar000000000 0000000 Certificate: Data: Version: 3 (0x2) Serial Number: 0 (0x0) Signature Algorithm: sha256WithRSAEncryption Issuer: O=Apache Software Foundation, OU=HttpComponents Project, CN=Test CA/emailAddress=dev@hc.apache.org Validity Not Before: Oct 20 19:34:55 2024 GMT Not After : Sep 26 19:34:55 2124 GMT Subject: O=Apache Software Foundation, OU=HttpComponents Project, CN=localhost/emailAddress=dev@hc.apache.org Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: 00:c9:0d:66:dc:9c:9c:57:43:83:ce:0a:09:d6:58: b3:05:3e:ed:c8:c4:b9:3e:79:66:22:dc:29:9b:a6: 8e:cc:22:b6:3d:96:d9:11:78:1c:cd:73:37:7a:5b: a6:91:32:4d:c4:11:ee:d0:97:5d:4e:89:0f:c7:cb: 79:40:e4:1e:d2:d7:f8:4f:0f:37:7e:04:71:a9:74: 40:e6:17:1c:94:65:f9:13:0d:52:bc:74:fe:6d:e6: ad:f0:07:60:81:e0:7e:b9:8c:52:8b:ca:ec:2f:04: 9f:5e:e5:2a:26:70:6d:e9:68:f1:9e:8c:d0:06:2f: 50:ee:98:03:d9:ba:82:5b:65:01:80:60:13:09:da: 64:be:ae:76:c4:ee:5c:05:fd:5d:35:fd:7e:91:29: 22:5d:f2:18:2f:49:f9:f9:c0:71:a0:85:26:60:09: d6:56:42:29:33:95:5b:e2:d8:02:af:89:02:f6:01: a7:cb:bc:90:bb:20:1e:6d:ee:9f:c0:ae:24:df:be: 30:5c:89:5d:c1:a4:21:a1:06:89:75:0a:16:f3:34: ef:fc:a3:a6:7d:9c:6a:97:28:59:1e:16:98:79:a1: 7e:b3:0a:63:7e:2d:4d:6c:eb:40:c6:c9:ea:63:01: 02:78:4a:4a:eb:98:8c:61:d7:d6:df:9a:cf:a4:8c: 32:21 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Basic Constraints: CA:FALSE Netscape Comment: OpenSSL Generated Certificate X509v3 Subject Key Identifier: 76:DC:5B:ED:CF:E7:5A:EC:05:2C:35:5B:42:25:EA:45:E8:18:AB:CE X509v3 Authority Key Identifier: 03:E4:E7:DA:0F:64:DB:13:1E:BD:85:AB:76:BC:29:CA:2F:A7:C7:4B X509v3 Subject Alternative Name: DNS:localhost Signature Algorithm: sha256WithRSAEncryption Signature Value: 09:f8:61:1a:59:c2:a4:28:f2:f0:b1:0a:41:59:14:f5:3a:32: f0:19:bc:d8:c0:21:94:d0:6e:e7:c3:7b:df:0f:52:b1:d6:b8: 78:5d:c5:d7:f4:f1:3d:85:ea:95:80:5c:eb:55:e0:8b:d3:02: ae:e5:50:85:0d:7e:98:fa:0d:1d:48:ff:74:ba:3b:c8:97:f9: c6:bb:ce:f1:1d:9e:80:f2:45:6c:14:e2:85:b0:5b:ea:9b:20: cf:d0:25:20:5d:41:51:c2:cc:a8:ff:b1:23:26:cd:c5:52:64: 5b:a1:2d:a6:27:e9:28:3f:09:d0:d9:b1:49:1c:1d:ad:a3:06: 3d:09:f0:dd:e6:fc:7c:83:b5:a5:e9:d7:99:9a:17:8e:e7:d4: 18:60:cf:14:56:6d:01:6b:9a:cf:9d:cd:1b:50:78:06:d9:4e: f2:d8:38:16:6a:5f:8b:07:44:ae:e9:92:5f:54:7c:9e:05:18: 5d:e1:e5:dd:83:88:8e:6c:02:32:a9:56:0d:55:06:2e:dc:27: 96:56:ed:65:59:c8:17:ac:18:f5:dd:65:b0:43:c3:d0:d1:6d: ca:2c:06:5e:9a:1d:33:33:57:da:aa:17:4e:a7:b2:63:93:45: ef:e6:b4:ca:93:d5:e8:e2:31:64:c8:26:7b:df:90:d8:b0:36: 48:3b:df:b1 -----BEGIN CERTIFICATE----- MIIEBjCCAu6gAwIBAgIBADANBgkqhkiG9w0BAQsFADB6MSMwIQYDVQQKDBpBcGFj aGUgU29mdHdhcmUgRm91bmRhdGlvbjEfMB0GA1UECwwWSHR0cENvbXBvbmVudHMg UHJvamVjdDEQMA4GA1UEAwwHVGVzdCBDQTEgMB4GCSqGSIb3DQEJARYRZGV2QGhj LmFwYWNoZS5vcmcwIBcNMjQxMDIwMTkzNDU1WhgPMjEyNDA5MjYxOTM0NTVaMHwx IzAhBgNVBAoMGkFwYWNoZSBTb2Z0d2FyZSBGb3VuZGF0aW9uMR8wHQYDVQQLDBZI dHRwQ29tcG9uZW50cyBQcm9qZWN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxIDAeBgkq hkiG9w0BCQEWEWRldkBoYy5hcGFjaGUub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEAyQ1m3JycV0ODzgoJ1lizBT7tyMS5PnlmItwpm6aOzCK2PZbZ EXgczXM3elumkTJNxBHu0JddTokPx8t5QOQe0tf4Tw83fgRxqXRA5hcclGX5Ew1S vHT+beat8AdggeB+uYxSi8rsLwSfXuUqJnBt6WjxnozQBi9Q7pgD2bqCW2UBgGAT Cdpkvq52xO5cBf1dNf1+kSkiXfIYL0n5+cBxoIUmYAnWVkIpM5Vb4tgCr4kC9gGn y7yQuyAebe6fwK4k374wXIldwaQhoQaJdQoW8zTv/KOmfZxqlyhZHhaYeaF+swpj fi1NbOtAxsnqYwECeEpK65iMYdfW35rPpIwyIQIDAQABo4GSMIGPMAkGA1UdEwQC MAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRl MB0GA1UdDgQWBBR23Fvtz+da7AUsNVtCJepF6BirzjAfBgNVHSMEGDAWgBQD5Ofa D2TbEx69hat2vCnKL6fHSzAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcN AQELBQADggEBAAn4YRpZwqQo8vCxCkFZFPU6MvAZvNjAIZTQbufDe98PUrHWuHhd xdf08T2F6pWAXOtV4IvTAq7lUIUNfpj6DR1I/3S6O8iX+ca7zvEdnoDyRWwU4oWw W+qbIM/QJSBdQVHCzKj/sSMmzcVSZFuhLaYn6Sg/CdDZsUkcHa2jBj0J8N3m/HyD taXp15maF47n1BhgzxRWbQFrms+dzRtQeAbZTvLYOBZqX4sHRK7pkl9UfJ4FGF3h 5d2DiI5sAjKpVg1VBi7cJ5ZW7WVZyBesGPXdZbBDw9DRbcosBl6aHTMzV9qqF06n smOTRe/mtMqT1ejiMWTIJnvfkNiwNkg737E= -----END CERTIFICATE----- httpcore5-testing/src/test/resources/docker/server-key.pem0100644 0000000 0000000 00000003250 00000250600 023001 0ustar000000000 0000000 -----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJDWbcnJxXQ4PO CgnWWLMFPu3IxLk+eWYi3Cmbpo7MIrY9ltkReBzNczd6W6aRMk3EEe7Ql11OiQ/H y3lA5B7S1/hPDzd+BHGpdEDmFxyUZfkTDVK8dP5t5q3wB2CB4H65jFKLyuwvBJ9e 5SomcG3paPGejNAGL1DumAPZuoJbZQGAYBMJ2mS+rnbE7lwF/V01/X6RKSJd8hgv Sfn5wHGghSZgCdZWQikzlVvi2AKviQL2AafLvJC7IB5t7p/AriTfvjBciV3BpCGh Bol1ChbzNO/8o6Z9nGqXKFkeFph5oX6zCmN+LU1s60DGyepjAQJ4SkrrmIxh19bf ms+kjDIhAgMBAAECggEAMWAwNdfkcW3sTcqbVd/cv1oQDbF6iQRJeCA3u7kw8qNH vGJylt63vCEiq9McWiZPgOqiiunRiXsRvBCVi8E6bK+LlOb4yuMOgv5A/gEJDMzq Wap4+j2FSrpPV1aIdf0LQkha8Wf5jyaEeUqwilYsOBmE1VphsFMofiApOeybHRSG 1PWcPXUYv/ylLHdEKJOy0eKgIkCSHfIyPzGVvVpfKCQYu9qQSXpx9ivNNvIrjgPZ i5lP3yYm7SKKGTb2J6eYsSVfvH8Q1bob8EV0ZJKfbqioZmwIQ0F+UCjR4j5HbYUR ygz2FQn9nOAqJaHbeKIj/qRi1V768TXMbW3H7cW5/QKBgQDkubAgEVhLwWJk8bTm sfrCpLkRaL0Dzen021sX9R5g3ajofL7INtDiCeP/5HSo8ffQdY9Q4CJjS12ekdjA F5sDT8px3phPPk2UEc1MtqeaXnL9coo243Mu7aENb1OsiDQKJ6TCXKE7QqR6sj9I B9rI36aKIweG6nVPVlzn0LREdwKBgQDhBvBQ1BAtvq/DXHdurqBdXpXIdS/xWc4E GVsKsInhg3Tr1PUhjwpIUG02uyIdJzy+6K2UjAqQoo4B5D55hWL4tHx0S2mt38g4 LCAs6s8Gr0E9Gnuwij5Up8bag3rTtMmjk0pFDcJtAQFMQ0WiXTkkgklKYuavCFIK h9rO6R1cJwKBgQC2lXR/ZNkzQCCnrDtYnWMr1grWVuHsE4hbqm/BZC7n7IpVbJ9v fDKq/nI/Z8Ooyd+lTPMnAITy9sq5Nnvse+uGbT+SPrsfJwEO3lcgkf6hQBxTLggf YNol8BPMgb4t0FyabqMbdI5QnBZoy7mwanTAPajYRLZRgQA8YRixBO2iaQKBgFGz xj9ir7kcOg7RnN+H8dvUwsd0nQKhW6arWh3oeTdzFlmmCZa0q8QTx4OOsFUrcOfT 7Wo46oEXND5Fk9Vlc8jfJyzGUMl7reOPSeNlIePIcARygCRaHUV5YT7nbAo/4tJM YTPvSf9v1PvOlRLdjCjQUTH79MvqVndSWkSz6SATAoGBAM0RSRuSOI+/AhPUKBc8 kknh5GF7fTAtAABwj5ruvqaNzTGwtJWyge9W+ZxuiCC+oFCMmslzOzvXVA/Uwmk0 BnPR5eZZm8/LwGHr0Npk8D9VrCE5KmyhpBFczXxVNXZ/KBduZu//UFxPhWim/bzl 3pArPBZ0o9eeHjxbch5ct2uF -----END PRIVATE KEY----- httpcore5-testing/src/test/resources/docker/server.p120100644 0000000 0000000 00000005343 00000250600 022041 0ustar000000000 0000000 0 0  *H   0 ~0 *H 00 *H 0W *H  0J0) *H  0kD(0 *H  0 `He*P{tJy2 p-Ʃ͔2 bT9i51d?z<ǣU]B}׌lcf=51P&~lP|0% RWc"BبY|h|k? d8A\}Nwq 1^6a1 |IcԻw%q Ey!oeZ.@竫(ۚ=Sg2/ AllowOverride none Require all denied DocumentRoot "/var/httpd/www" Options Indexes FollowSymLinks AllowOverride None Require all granted ServerName localhost:80 Protocols http/1.1 httpcore5-testing/src/test/resources/docker/httpd/httpd-h2c.conf0100644 0000000 0000000 00000002164 00000250600 023774 0ustar000000000 0000000 # 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. Listen 81 ServerName localhost:81 Protocols h2c LogLevel http2:info H2Push on H2EarlyHints on H2PushResource /aaa H2PushResource /bbb H2PushResource /ccc httpcore5-testing/src/test/resources/docker/httpd/httpd-ssl.conf0100644 0000000 0000000 00000003132 00000250600 024115 0ustar000000000 0000000 # 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. # Listen 443 SSLCipherSuite HIGH:MEDIUM:!MD5:!RC4:!3DES SSLProxyCipherSuite HIGH:MEDIUM:!MD5:!RC4:!3DES SSLHonorCipherOrder on SSLProtocol all -SSLv3 SSLProxyProtocol all -SSLv3 SSLPassPhraseDialog builtin SSLSessionCacheTimeout 300 ServerName localhost:443 Protocols h2 http/1.1 SSLEngine on SSLCertificateFile "/usr/local/apache2/conf/server-cert.pem" SSLCertificateKeyFile "/usr/local/apache2/conf/server-key.pem" CustomLog /proc/self/fd/1 \ "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b" LogLevel http2:info H2Push on H2EarlyHints on H2PushResource /aaa H2PushResource /bbb H2PushResource /ccc httpcore5-testing/src/test/resources/docker/nginx/0040755 0000000 0000000 00000000000 00000250600 021330 5ustar000000000 0000000 httpcore5-testing/src/test/resources/docker/nginx/default.conf0100644 0000000 0000000 00000001770 00000250600 023625 0ustar000000000 0000000 # 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. server { listen 80; server_name localhost; location / { root /var/nginx/www; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } httpcore5-testing/src/test/resources/docker/nginx/h2c.conf0100644 0000000 0000000 00000002221 00000250600 022645 0ustar000000000 0000000 # 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. server { listen 81 http2; server_name localhost; location / { root /var/nginx/www; location = /pushy { http2_push "/aaa"; http2_push "/bbb"; http2_push "/ccc"; } } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } httpcore5-testing/src/test/resources/docker/nginx/ssl.conf0100644 0000000 0000000 00000002520 00000250600 022774 0ustar000000000 0000000 # 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. server { listen 443 ssl http2; server_name localhost; ssl_certificate /etc/nginx/server-cert.pem; ssl_certificate_key /etc/nginx/server-key.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; location / { root /var/nginx/www; location = /pushy { http2_push "/aaa"; http2_push "/bbb"; http2_push "/ccc"; } } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } src/0040755 0000000 0000000 00000000000 00000250600 010355 5ustar000000000 0000000 src/site/0040755 0000000 0000000 00000000000 00000250600 011321 5ustar000000000 0000000 src/site/site.xml0100644 0000000 0000000 00000005665 00000250600 013020 0ustar000000000 0000000 org.apache.maven.skins maven-fluido-skin 1.9 Apache https://www.apache.org/images/asf_logo.gif https://www.apache.org/ HttpComponents https://hc.apache.org/images/logos/httpcomponents.png https://hc.apache.org/
Apache HttpComponents, Commons HttpClient, Apache, the Apache feather logo, and the Apache HttpComponents project logo are trademarks of The Apache Software Foundation.

All other marks mentioned may be trademarks or registered trademarks of their respective owners.
true
src/site/resources/0040755 0000000 0000000 00000000000 00000250600 013333 5ustar000000000 0000000 src/site/resources/css/0040755 0000000 0000000 00000000000 00000250600 014123 5ustar000000000 0000000 src/site/resources/css/site.css0100644 0000000 0000000 00000001514 00000250600 015577 0ustar000000000 0000000 /* * 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. */ @import url("/css/hc-maven.css"); test-CA/0040755 0000000 0000000 00000000000 00000250600 011026 5ustar000000000 0000000 test-CA/README.txt0100644 0000000 0000000 00000000456 00000250600 012526 0ustar000000000 0000000 This directory contains CA key and certificate for unit and integration tests --- Use this command to check the private key Passphrase: nopassword --- openssl rsa -in ca-key.pem -check -text -noout --- Use this command to print CA certificate details --- openssl x509 -in ca-cert.pem -text -noout ---test-CA/ca-cert.pem0100644 0000000 0000000 00000002537 00000250600 013053 0ustar000000000 0000000 -----BEGIN CERTIFICATE----- MIIDyTCCArGgAwIBAgIJAO3mCIu9mboMMA0GCSqGSIb3DQEBCwUAMHoxIzAhBgNV BAoMGkFwYWNoZSBTb2Z0d2FyZSBGb3VuZGF0aW9uMR8wHQYDVQQLDBZIdHRwQ29t cG9uZW50cyBQcm9qZWN0MRAwDgYDVQQDDAdUZXN0IENBMSAwHgYJKoZIhvcNAQkB FhFkZXZAaGMuYXBhY2hlLm9yZzAgFw0xNDEwMTMxNTAxMjBaGA8yMjg4MDcyODE1 MDEyMFowejEjMCEGA1UECgwaQXBhY2hlIFNvZnR3YXJlIEZvdW5kYXRpb24xHzAd BgNVBAsMFkh0dHBDb21wb25lbnRzIFByb2plY3QxEDAOBgNVBAMMB1Rlc3QgQ0Ex IDAeBgkqhkiG9w0BCQEWEWRldkBoYy5hcGFjaGUub3JnMIIBIjANBgkqhkiG9w0B AQEFAAOCAQ8AMIIBCgKCAQEApXhHtKRvAxbLI+f21zNe68dkVXAhSMIfHQJGb2en S1H8yE4HPIb4vPQ0U7fQCb7RXplm6cHExpof4cO3DmyqD5KeQk0TdM8XrhviDgwj Y0KQ/lgwGHR5CpYoZ6LYWaLSE/wt9dVu80UcK8a3hW9G0X/4b79fMO6HYDix+CI4 b17sqZ4K0tWKA10Xe+2RJU8Y01pPBaPR/UsAn+a1pZ6f8BhL879oWHfLWKcgZOYP U4sYED0S8gs4/ED1zRj2/uHb313sHTl+OU4X5v+OvwBvbNBrl5qfMTZnRNxlOfRq UTJdcopsp2aNeqHiorSDOrHwMIJpxQ2XqHT2l9s8msXf4wIDAQABo1AwTjAdBgNV HQ4EFgQUA+Tn2g9k2xMevYWrdrwpyi+nx0swHwYDVR0jBBgwFoAUA+Tn2g9k2xMe vYWrdrwpyi+nx0swDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAFVEp 8Nv6JoFY7oGgu6068fH/kq7A3SllqMpbv7dddI9fgwy352cBtg6PkYkGtEE037xs FQSYV1NiAkNWTJER+Q+kVbQrhuPNKZqh1g0sUKwv3X20BmgJ9hbU9klWZjdjujyd h9Ybjuntkn5XPp1zN6zHD0sQReEJnRlD6FT1axrQWpICzE4qoo8k64G+6/rqFywc oMc/Of3KCAHjtbWklEu97hjBvGC/nEP4/VhRrjWWSeGHv88LCyO/Yg6v3zrZHFLW +KhsDCPyLxSSISFskLQfukiqf2lr87kQq/oF27sAr3sR3Jqh4qzflM2XLgjmZuRE OrHT6lvUemRyksA5qg== -----END CERTIFICATE----- test-CA/ca-key.pem0100644 0000000 0000000 00000003452 00000250600 012703 0ustar000000000 0000000 -----BEGIN ENCRYPTED PRIVATE KEY----- MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIq0bLh96mWv4CAggA MBQGCCqGSIb3DQMHBAimZqUiELx13QSCBMgaLWrGFqveIzwQUsebS6FBdVq0lodz Vlekje8ycFDYSd21V9jPMwrSupZceeBQjCrpyLZ3oPkR+MvObmznev8XYcJzVCkF E9ApAaHZe248wWcu1/D7auHNG3GyZfvYS0c//Rs2OzMZfsUvX93RVullCRREvCYS qXhaO3ywFocndKRpSnkOBs2SRa0yc9POl4n4dwyKhsJUaMSmhPbJr9UBvCbXHZIA gLcSWzVon3EtZCSubMp9eo90G5MzIXEyPBTcIHwpyqRWTkaTUTq4R0c4/RTX+l7K OZuRIEeBEW6z009fSagymN/KEH3gUkg5pG6i1YWF63OVKTMGn+yQGWwYXwTyEGi5 HZpD98wh3ycucmL93XLk+yYXQcTp1i+u4GaXNWGREQvNW6onCGeg6WWj1PrIsqoi TZ2pgQUJWPR1K3037hY0o9sakAkyYSyTPVvHOUcbf3+GhqGS1FsSNOxKRNpYm/3v Gf0SUN8BavPliK9NSU5JAbprr/hoL5o72dCX9DiOgwfW3HyD/gLh7sVyVBdAzTnE XFaYFnrb5QnqHbgWvaLbJUT5K7MW3OFLVConydYtYdaUl5z49OflhgnvYOPgTSUr k9c7exQjedAduPd8dXODh9l2g+QEXJoT+YYFEYHkQlsZgH1hCLXD1TmAeI4LMklb vPaGE8Ouj1pfbejdTNsqLfW0IiR/jZzEjRgqrueMf2VUjtqTZyPayc2rU4kOoKhv JzQ0wOFhgRztWJy2voRe+iYss3ToqZ7qLpjBfCTsxCJqbuaGeJWWSnOlDpSysgr+ q4BvCzDcvf/0mKD2cQuJx/kynQMCcWB/VegRsQ24Y+3T7IU1w8ccmRfSZ93AwkAh MKJzKaVhD/gn9vUG/we18p7RMIc9pk1o2Z2Ru3mKjkO3QYRP6Y7yk0ah2JKrHIPf LWfPuHmtzHQXkY3RbVvxvwD/+qHm8ogXq52w8cpGhY5UwAEHrLLwypdBHccrAJjo bE13M/MrtTry/k8OMRqhhRzHUXBq6mLaWffCaP2SAVfJEez2iASvGJFvgy3bSkWY rwWMSfZKDkauwDMW5gpFrpeuqgD64LO72sN01riVDpaEyNODRCEEBGce+O+91R9K TLVgRYFsxClyZy1nynD66gkTepEm1yOgcdqV3651Os+TGm39jGYHy1k9mPz8ypqf 8n8uw4nV3SbIwfpy4Z8onHixfc/Fugm7yQHW4dSuCpahyIJHom6Cq7SZfPuo9e3t 8tqaxvK4U/dAXoimvN1eakH2FoVFIj3mk7OAKBgmDINH9GlzXPwRsTfiJSP4Xaod ouWIQLLeXQuuOc5VJd1Xex75o8ciSOomAS0uR4Fvk/2NkAm0EMddjZnuWLQaXPry JiUIgSx3w3yRq9RSQOxDRQpp2nP2roX7cyeGPzTmeujikExGTa3YBxuAShDLx5pt fpi0ol8H8ohDU4eV9pv96KRBG9e8sQf1zpGjeYLTFiN35IQxYJx3HTXp9/oFWkmA OdCEwggIKJ/RtgkWOWogTilQVA41p4XZr661fxoSE86sHXkZKn8IGnAKLFT46nWM IYVDalYUiSNZr+KbzmLIV3LmYE3mlqGI4vDvQtd9zQk/uatYBc2DetuTWPZHCEKS 3Nk= -----END ENCRYPTED PRIVATE KEY----- test-CA/openssl.cnf0100644 0000000 0000000 00000026065 00000250600 013207 0ustar000000000 0000000 # 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 definition stops the following lines choking if HOME isn't # defined. HOME = . RANDFILE = $ENV::HOME/.rnd # Extra OBJECT IDENTIFIER info: #oid_file = $ENV::HOME/.oid oid_section = new_oids # To use this configuration file with the "-extfile" option of the # "openssl x509" utility, name here the section containing the # X.509v3 extensions to use: # extensions = # (Alternatively, use a configuration file that has only # X.509v3 extensions in its main [= default] section.) [ new_oids ] # We can add new OIDs in here for use by 'ca', 'req' and 'ts'. # Add a simple OID like this: # testoid1=1.2.3.4 # Or use config file substitution like this: # testoid2=${testoid1}.5.6 # Policies used by the TSA examples. tsa_policy1 = 1.2.3.4.1 tsa_policy2 = 1.2.3.4.5.6 tsa_policy3 = 1.2.3.4.5.7 #################################################################### [ ca ] default_ca = CA_default # The default ca section #################################################################### [ CA_default ] dir = ./test-CA # Where everything is kept certs = $dir/certs # Where the issued certs are kept crl_dir = $dir/crl # Where the issued crl are kept database = $dir/index.txt # database index file. #unique_subject = no # Set to 'no' to allow creation of # several ctificates with same subject. new_certs_dir = $dir/newcerts # default place for new certs. certificate = $dir/ca-cert.pem # The CA certificate serial = $dir/serial.txt # The current serial number crlnumber = $dir/crlnumber # the current crl number # must be commented out to leave a V1 CRL crl = $dir/crl.pem # The current CRL private_key = $dir/ca-key.pem# The private key RANDFILE = $dir/.rand # private random number file x509_extensions = usr_cert # The extentions to add to the cert # Comment out the following two lines for the "traditional" # (and highly broken) format. name_opt = ca_default # Subject Name options cert_opt = ca_default # Certificate field options # Extension copying option: use with caution. copy_extensions = copy # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs # so this is commented out by default to leave a V1 CRL. # crlnumber must also be commented out to leave a V1 CRL. # crl_extensions = crl_ext default_days = 365 # how long to certify for default_crl_days= 30 # how long before next CRL default_md = default # use public key default MD preserve = no # keep passed DN ordering # A few difference way of specifying how similar the request should look # For type CA, the listed attributes must be the same, and the optional # and supplied fields are just that :-) policy = policy_match # For the CA policy [ policy_match ] countryName = optional stateOrProvinceName = optional organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional # For the 'anything' policy # At this point in time, you must list all acceptable 'object' # types. [ policy_anything ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional #################################################################### [ req ] default_bits = 2048 default_keyfile = privkey.pem distinguished_name = req_distinguished_name attributes = req_attributes x509_extensions = v3_ca # The extentions to add to the self signed cert # Passwords for private keys if not present they will be prompted for # input_password = secret # output_password = secret # This sets a mask for permitted string types. There are several options. # default: PrintableString, T61String, BMPString. # pkix : PrintableString, BMPString (PKIX recommendation before 2004) # utf8only: only UTF8Strings (PKIX recommendation after 2004). # nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). # MASK:XXXX a literal mask value. # WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings. string_mask = utf8only # req_extensions = v3_req # The extensions to add to a certificate request [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = Some-State localityName = Locality Name (eg, city) 0.organizationName = Organization Name (eg, company) 0.organizationName_default = Apache Software Foundation organizationalUnitName = Organizational Unit Name (eg, section) organizationalUnitName_default = HttpComponents Project commonName = Common Name (e.g. server FQDN or YOUR name) commonName_max = 64 emailAddress = Email Address emailAddress_max = 64 # SET-ex3 = SET extension number 3 [ req_attributes ] challengePassword = A challenge password challengePassword_min = 4 challengePassword_max = 20 unstructuredName = An optional company name [ usr_cert ] # These extensions are added when 'ca' signs a request. # This goes against PKIX guidelines but some CAs do it and some software # requires this to avoid interpreting an end user certificate as a CA. basicConstraints=CA:FALSE # Here are some examples of the usage of nsCertType. If it is omitted # the certificate can be used for anything *except* object signing. # This is OK for an SSL server. # nsCertType = server # For an object signing certificate this would be used. # nsCertType = objsign # For normal client use this is typical # nsCertType = client, email # and for everything including object signing: # nsCertType = client, email, objsign # This is typical in keyUsage for a client certificate. # keyUsage = nonRepudiation, digitalSignature, keyEncipherment # This will be displayed in Netscape's comment listbox. nsComment = "OpenSSL Generated Certificate" # PKIX recommendations harmless if included in all certificates. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid,issuer # This stuff is for subjectAltName and issuerAltname. # Import the email address. # subjectAltName=email:copy # An alternative to produce certificates that aren't # deprecated according to PKIX. # subjectAltName=email:move # Copy subject details # issuerAltName=issuer:copy #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem #nsBaseUrl #nsRevocationUrl #nsRenewalUrl #nsCaPolicyUrl #nsSslServerName # This is required for TSA certificates. # extendedKeyUsage = critical,timeStamping [ v3_req ] # Extensions to add to a certificate request basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment [ v3_ca ] # Extensions for a typical CA # PKIX recommendation. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid:always,issuer # This is what PKIX recommends but some broken software chokes on critical # extensions. #basicConstraints = critical,CA:true # So we do this instead. basicConstraints = CA:true # Key usage: this is typical for a CA certificate. However since it will # prevent it being used as an test self-signed certificate it is best # left out by default. # keyUsage = cRLSign, keyCertSign # Some might want this also # nsCertType = sslCA, emailCA # Include email address in subject alt name: another PKIX recommendation # subjectAltName=email:copy # Copy issuer details # issuerAltName=issuer:copy # DER hex encoding of an extension: beware experts only! # obj=DER:02:03 # Where 'obj' is a standard or added object # You can even override a supported extension: # basicConstraints= critical, DER:30:03:01:01:FF [ crl_ext ] # CRL extensions. # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. # issuerAltName=issuer:copy authorityKeyIdentifier=keyid:always [ proxy_cert_ext ] # These extensions should be added when creating a proxy certificate # This goes against PKIX guidelines but some CAs do it and some software # requires this to avoid interpreting an end user certificate as a CA. basicConstraints=CA:FALSE # Here are some examples of the usage of nsCertType. If it is omitted # the certificate can be used for anything *except* object signing. # This is OK for an SSL server. # nsCertType = server # For an object signing certificate this would be used. # nsCertType = objsign # For normal client use this is typical # nsCertType = client, email # and for everything including object signing: # nsCertType = client, email, objsign # This is typical in keyUsage for a client certificate. # keyUsage = nonRepudiation, digitalSignature, keyEncipherment # This will be displayed in Netscape's comment listbox. nsComment = "OpenSSL Generated Certificate" # PKIX recommendations harmless if included in all certificates. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid,issuer # This stuff is for subjectAltName and issuerAltname. # Import the email address. # subjectAltName=email:copy # An alternative to produce certificates that aren't # deprecated according to PKIX. # subjectAltName=email:move # Copy subject details # issuerAltName=issuer:copy #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem #nsBaseUrl #nsRevocationUrl #nsRenewalUrl #nsCaPolicyUrl #nsSslServerName # This really needs to be in place for it to be a proxy certificate. proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo #################################################################### [ tsa ] default_tsa = tsa_config1 # the default TSA section [ tsa_config1 ] # These are used by the TSA reply generation only. dir = ./demoCA # TSA root directory serial = $dir/tsaserial # The current serial number (mandatory) crypto_device = builtin # OpenSSL engine to use for signing signer_cert = $dir/tsacert.pem # The TSA signing certificate # (optional) certs = $dir/cacert.pem # Certificate chain to include in reply # (optional) signer_key = $dir/private/tsakey.pem # The TSA private key (optional) default_policy = tsa_policy1 # Policy if request did not specify it # (optional) other_policies = tsa_policy2, tsa_policy3 # acceptable policies (optional) digests = md5, sha1 # Acceptable message digests (mandatory) accuracy = secs:1, millisecs:500, microsecs:100 # (optional) clock_precision_digits = 0 # number of digits after dot. (optional) ordering = yes # Is ordering defined for timestamps? # (optional, default: no) tsa_name = yes # Must the TSA name be included in the reply? # (optional, default: no) ess_cert_id_chain = no # Must the ESS cert id chain be included? # (optional, default: no) httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/ep/0040755 0000000 0000000 00000000000 00000250600 023313 5ustar000000000 0000000 test-CA/newcerts/0040755 0000000 0000000 00000000000 00000250600 012660 5ustar000000000 0000000