diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 64e47283ef..75551894c9 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -60,11 +60,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -87,4 +87,4 @@ jobs: - run: mvn clean package -DskipTests -Drat.skip=true -Dcheckstyle.skip - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/depsreview.yaml b/.github/workflows/depsreview.yaml index 83886a45a3..0748cf09ac 100644 --- a/.github/workflows/depsreview.yaml +++ b/.github/workflows/depsreview.yaml @@ -26,6 +26,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Dependency Review' - uses: actions/dependency-review-action@v3 + uses: actions/dependency-review-action@v4 diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 8a977c7aa2..187832dab4 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -24,36 +24,27 @@ jobs: build: runs-on: ${{ matrix.os }} - continue-on-error: ${{ matrix.experimental }} strategy: matrix: # windows-latest is not used due to intermittent network failures os: [ubuntu-latest, macos-latest] # All LTS versions plus the current version - java: [ 8, 11, 17 ] - experimental: [false] -# include: -# - java: 20-ea -# os: ubuntu-latest -# experimental: true -# - java: 20-ea -# os: windows-latest -# experimental: true -# - java: 20-ea -# os: macos-latest -# experimental: true + java: [ 11, 17, 21 ] + include: + - java: 8 + os: ubuntu-latest fail-fast: false steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 + - uses: actions/checkout@v4 + - uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: ${{ matrix.java }} diff --git a/NOTICE.txt b/NOTICE.txt index 2670476387..34ee2871eb 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,5 +1,5 @@ Apache HttpComponents Core -Copyright 2005-2022 The Apache Software Foundation +Copyright 2005-2024 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt index 67a7224ab7..13d88258ef 100644 --- a/RELEASE_NOTES.txt +++ b/RELEASE_NOTES.txt @@ -1,3 +1,231 @@ +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 ------------------ @@ -101,7 +329,7 @@ Change Log * 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 connect. +* 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. @@ -658,7 +886,7 @@ Change Log Contributed by Lee Ray * Added exception callback to async server implementations enabling logging of unexpected and - fatal exceptions in the server side protocol handlers. + fatal exceptions in the server-side protocol handlers. Contributed by Oleg Kalnichevski @@ -1834,7 +2062,7 @@ Change Log * HTTPCORE-424: added ConnPool policy parameter to control connection re-use policy. Contributed by Oleg Kalnichevski -* HTTPCORE-413: Minimal chunk side can now be specified as H1Config#chunkSizeHint. +* 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 @@ -2056,7 +2284,7 @@ the 4.4 series are: * Support for pipelined request processing on the server-side -* Support for pipelined request execution on the client side +* Support for pipelined request execution on the client-side * Simplified bootstrapping of blocking and non-blocking (NIO) HTTP server implementations @@ -2083,7 +2311,7 @@ the 4.4 series are: * Support for pipelined request processing on the server-side -* Support for pipelined request execution on the client side +* Support for pipelined request execution on the client-side * Simplified bootstrapping of blocking and non-blocking (NIO) HTTP server implementations @@ -2105,7 +2333,7 @@ this release are: * Support for pipelined request processing on the server-side -* Support for pipelined request execution on the client side +* Support for pipelined request execution on the client-side * Simplified bootstrapping of blocking and non-blocking (NIO) HTTP server implementations @@ -2946,7 +3174,7 @@ 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. +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. @@ -3358,7 +3586,7 @@ a small number of I/O threads. 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. +* [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. diff --git a/httpcore5-h2/pom.xml b/httpcore5-h2/pom.xml index 9f34b2d972..5ca4d6a1b5 100644 --- a/httpcore5-h2/pom.xml +++ b/httpcore5-h2/pom.xml @@ -28,7 +28,7 @@ org.apache.httpcomponents.core5 httpcore5-parent - 5.2.4-SNAPSHOT + 5.3 httpcore5-h2 Apache HttpComponents Core HTTP/2 diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/FifoLinkedList.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/FifoLinkedList.java index f6f1422b4d..195a162743 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/FifoLinkedList.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/FifoLinkedList.java @@ -27,6 +27,8 @@ 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; @@ -138,7 +140,7 @@ public int getIndex() { @Override public String toString() { return "[" + - (header != null ? header.toString() : "master") + + Objects.toString(header, "master") + "; seqNum=" + seqNum + "; previous=" + (previous != null ? previous.header : null) + "; next=" + (next != null ? next.header : null) + diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackDecoder.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackDecoder.java index 3f35676b2a..880d0ccbde 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackDecoder.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackDecoder.java @@ -158,6 +158,10 @@ void decodeString(final ByteArrayBuffer buffer, final ByteBuffer src) throws HPa } } + int getTmpBufSize() { + return tmpBuf == null ? 0 : tmpBuf.capacity(); + } + private void clearState() { if (this.tmpBuf != null) { @@ -182,7 +186,7 @@ private void ensureCapacity(final int extra) { if (this.tmpBuf == null) { this.tmpBuf = CharBuffer.allocate(Math.max(256, extra)); } - final int requiredCapacity = this.tmpBuf.remaining() + extra; + final int requiredCapacity = this.tmpBuf.position() + extra; if (requiredCapacity > this.tmpBuf.capacity()) { expandCapacity(requiredCapacity); } diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/DefaultH2RequestConverter.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/DefaultH2RequestConverter.java index d8f2611c6e..c1ad90ca55 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/DefaultH2RequestConverter.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/DefaultH2RequestConverter.java @@ -39,6 +39,7 @@ 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; @@ -137,6 +138,7 @@ public HttpRequest convert(final List
headers) throws HttpException { 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); @@ -208,4 +210,39 @@ public List
convert(final HttpRequest message) throws HttpException { 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 '/'"); + } + } + } + } + } } diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/H2Processors.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/H2Processors.java index e1969a62f7..5a82d83a05 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/H2Processors.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/H2Processors.java @@ -29,8 +29,10 @@ 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.H2RequestConnControl; @@ -52,13 +54,15 @@ public final class H2Processors { public static HttpProcessorBuilder customServer(final String serverInfo) { return HttpProcessorBuilder.create() .addAll( - new ResponseDate(), + ResponseConformance.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); + H2RequestValidateHost.INSTANCE, + RequestConformance.INSTANCE); } public static HttpProcessor server(final String serverInfo) { @@ -72,8 +76,8 @@ public static HttpProcessor server() { public static HttpProcessorBuilder customClient(final String agentInfo) { return HttpProcessorBuilder.create() .addAll( - H2RequestContent.INSTANCE, H2RequestTargetHost.INSTANCE, + H2RequestContent.INSTANCE, H2RequestConnControl.INSTANCE, new RequestUserAgent(!TextUtils.isBlank(agentInfo) ? agentInfo : VersionInfo.getSoftwareInfo(SOFTWARE, "org.apache.hc.core5", HttpProcessors.class)), diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java index 03ac7302ee..431ee5b0b6 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java @@ -42,6 +42,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; +import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; import org.apache.hc.core5.concurrent.CancellableDependency; @@ -698,6 +699,8 @@ public final void onException(final Exception cause) { 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 { @@ -1142,7 +1145,7 @@ private void consumeContinuationFrame(final RawFrame frame, final H2Stream strea } } - private void consumeSettingsFrame(final ByteBuffer payload) throws HttpException, IOException { + private void consumeSettingsFrame(final ByteBuffer payload) throws IOException { final H2Config.Builder configBuilder = H2Config.initial(); while (payload.hasRemaining()) { final int code = payload.getShort(); @@ -1420,8 +1423,8 @@ public void push(final List
headers, final AsyncPushProducer pushProduce localConfig.getInitialWindowSize(), remoteConfig.getInitialWindowSize()); final HttpCoreContext context = HttpCoreContext.create(); - context.setAttribute(HttpCoreContext.SSL_SESSION, getSSLSession()); - context.setAttribute(HttpCoreContext.CONNECTION_ENDPOINT, getEndpointDetails()); + context.setSSLSession(getSSLSession()); + context.setEndpointDetails(getEndpointDetails()); final H2StreamHandler streamHandler = new ServerPushH2StreamHandler( channel, httpProcessor, connMetrics, pushProducer, context); final H2Stream stream = new H2Stream(channel, streamHandler, false); diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2PrefaceHandler.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2PrefaceHandler.java index 9730b3a38f..41d3800ca6 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2PrefaceHandler.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2PrefaceHandler.java @@ -104,7 +104,10 @@ private void initialize() throws IOException { ioSession.setEvent(SelectionKey.OP_WRITE); } - private void writeOutPreface(final IOSession session) throws IOException { + /** + * @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); } @@ -115,8 +118,9 @@ private void writeOutPreface(final IOSession session) throws IOException { if (inBuf != null) { inBuf.clear(); } - preface = null; + return true; } + return false; } @Override @@ -131,8 +135,11 @@ public void outputReady(final IOSession session) throws IOException { if (initialized.compareAndSet(false, true)) { initialize(); } + final ByteBuffer preface = this.preface; if (preface != null) { - writeOutPreface(session); + if (writeOutPreface(session, preface)) { + this.preface = null; + } } else { throw new ProtocolNegotiationException("Unexpected output"); } @@ -146,8 +153,11 @@ public void inputReady(final IOSession session, final ByteBuffer src) throws IOE } inBuf.put(src); } + final ByteBuffer preface = this.preface; if (preface != null) { - writeOutPreface(session); + if (writeOutPreface(session, preface)) { + this.preface = null; + } } else { throw new ProtocolNegotiationException("Unexpected input"); } diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2StreamHandler.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2StreamHandler.java index 732e60c732..44271518c3 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2StreamHandler.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2StreamHandler.java @@ -110,9 +110,9 @@ public void endStream() throws IOException { this.exchangeHandler = exchangeHandler; this.pushHandlerFactory = pushHandlerFactory; this.context = context; - this.requestCommitted = new AtomicBoolean(false); - this.failed = new AtomicBoolean(false); - this.done = new AtomicBoolean(false); + this.requestCommitted = new AtomicBoolean(); + this.failed = new AtomicBoolean(); + this.done = new AtomicBoolean(); this.requestState = MessageState.HEADERS; this.responseState = MessageState.HEADERS; } @@ -137,7 +137,7 @@ public boolean isOutputReady() { private void commitRequest(final HttpRequest request, final EntityDetails entityDetails) throws HttpException, IOException { if (requestCommitted.compareAndSet(false, true)) { context.setProtocolVersion(HttpVersion.HTTP_2); - context.setAttribute(HttpCoreContext.HTTP_REQUEST, request); + context.setRequest(request); httpProcessor.process(request, entityDetails, context); @@ -205,7 +205,7 @@ public void consumeHeader(final List
headers, final boolean endStream) t } final EntityDetails entityDetails = endStream ? null : new IncomingEntityDetails(response, -1); - context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response); + context.setResponse(response); httpProcessor.process(response, entityDetails, context); connMetrics.incrementResponseCount(); diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2StreamMultiplexer.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2StreamMultiplexer.java index c53adb049d..b7850090b9 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2StreamMultiplexer.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2StreamMultiplexer.java @@ -111,9 +111,9 @@ H2StreamHandler createLocallyInitiatedStream( final RequestExecutionCommand executionCommand = (RequestExecutionCommand) command; final AsyncClientExchangeHandler exchangeHandler = executionCommand.getExchangeHandler(); final HandlerFactory pushHandlerFactory = executionCommand.getPushHandlerFactory(); - final HttpCoreContext context = HttpCoreContext.adapt(executionCommand.getContext()); - context.setAttribute(HttpCoreContext.SSL_SESSION, getSSLSession()); - context.setAttribute(HttpCoreContext.CONNECTION_ENDPOINT, getEndpointDetails()); + final HttpCoreContext context = HttpCoreContext.castOrCreate(executionCommand.getContext()); + context.setSSLSession(getSSLSession()); + context.setEndpointDetails(getEndpointDetails()); return new ClientH2StreamHandler(channel, httpProcessor, connMetrics, exchangeHandler, pushHandlerFactory != null ? pushHandlerFactory : this.pushHandlerFactory, context); @@ -128,8 +128,8 @@ H2StreamHandler createRemotelyInitiatedStream( final BasicHttpConnectionMetrics connMetrics, final HandlerFactory pushHandlerFactory) throws IOException { final HttpCoreContext context = HttpCoreContext.create(); - context.setAttribute(HttpCoreContext.SSL_SESSION, getSSLSession()); - context.setAttribute(HttpCoreContext.CONNECTION_ENDPOINT, getEndpointDetails()); + context.setSSLSession(getSSLSession()); + context.setEndpointDetails(getEndpointDetails()); return new ClientPushH2StreamHandler(channel, httpProcessor, connMetrics, pushHandlerFactory != null ? pushHandlerFactory : this.pushHandlerFactory, context); diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientPushH2StreamHandler.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientPushH2StreamHandler.java index 1c9c1ae982..d911c689ec 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientPushH2StreamHandler.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientPushH2StreamHandler.java @@ -78,8 +78,8 @@ class ClientPushH2StreamHandler implements H2StreamHandler { this.connMetrics = connMetrics; this.pushHandlerFactory = pushHandlerFactory; this.context = context; - this.failed = new AtomicBoolean(false); - this.done = new AtomicBoolean(false); + this.failed = new AtomicBoolean(); + this.done = new AtomicBoolean(); this.requestState = MessageState.HEADERS; this.responseState = MessageState.HEADERS; } @@ -115,7 +115,7 @@ public void consumePromise(final List
headers) throws HttpException, IOE } context.setProtocolVersion(HttpVersion.HTTP_2); - context.setAttribute(HttpCoreContext.HTTP_REQUEST, request); + context.setRequest(request); httpProcessor.process(request, null, context); connMetrics.incrementRequestCount(); @@ -134,7 +134,7 @@ public void consumeHeader(final List
headers, final boolean endStream) t final HttpResponse response = DefaultH2ResponseConverter.INSTANCE.convert(headers); final EntityDetails entityDetails = endStream ? null : new IncomingEntityDetails(request, -1); - context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response); + context.setResponse(response); httpProcessor.process(response, entityDetails, context); connMetrics.incrementResponseCount(); diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/FrameInputBuffer.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/FrameInputBuffer.java index c0557aecea..8708b07233 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/FrameInputBuffer.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/FrameInputBuffer.java @@ -101,9 +101,8 @@ public void put(final ByteBuffer src) { * * @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 { @@ -190,6 +189,8 @@ public RawFrame read(final ByteBuffer src, final ReadableByteChannel channel) th * 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); diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamHandler.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamHandler.java index 27446e80b1..b754e8e67b 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamHandler.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamHandler.java @@ -123,7 +123,6 @@ public void sendInformation(final HttpResponse response, final HttpContext httpC @Override public void sendResponse( final HttpResponse response, final EntityDetails responseEntityDetails, final HttpContext httpContext) throws HttpException, IOException { - ServerSupport.validateResponse(response, responseEntityDetails); commitResponse(response, responseEntityDetails); } @@ -138,9 +137,9 @@ public void pushPromise( this.connMetrics = connMetrics; this.exchangeHandlerFactory = exchangeHandlerFactory; this.context = context; - this.responseCommitted = new AtomicBoolean(false); - this.failed = new AtomicBoolean(false); - this.done = new AtomicBoolean(false); + this.responseCommitted = new AtomicBoolean(); + this.failed = new AtomicBoolean(); + this.done = new AtomicBoolean(); this.requestState = MessageState.HEADERS; this.responseState = MessageState.IDLE; } @@ -171,7 +170,7 @@ private void commitResponse( if (status < HttpStatus.SC_SUCCESS) { throw new HttpException("Invalid response: " + status); } - context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response); + context.setResponse(response); httpProcessor.process(response, responseEntityDetails, context); final List
responseHeaders = DefaultH2ResponseConverter.INSTANCE.convert(response); @@ -231,7 +230,7 @@ public void consumeHeader(final List
headers, final boolean endStream) t exchangeHandler = handler; context.setProtocolVersion(HttpVersion.HTTP_2); - context.setAttribute(HttpCoreContext.HTTP_REQUEST, request); + context.setRequest(request); try { httpProcessor.process(request, requestEntityDetails, context); diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamMultiplexer.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamMultiplexer.java index ecbb9d6662..2fe8f45273 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamMultiplexer.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamMultiplexer.java @@ -105,8 +105,8 @@ H2StreamHandler createRemotelyInitiatedStream( final BasicHttpConnectionMetrics connMetrics, final HandlerFactory pushHandlerFactory) throws IOException { final HttpCoreContext context = HttpCoreContext.create(); - context.setAttribute(HttpCoreContext.SSL_SESSION, getSSLSession()); - context.setAttribute(HttpCoreContext.CONNECTION_ENDPOINT, getEndpointDetails()); + context.setSSLSession(getSSLSession()); + context.setEndpointDetails(getEndpointDetails()); return new ServerH2StreamHandler(channel, httpProcessor, connMetrics, exchangeHandlerFactory, context); } diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerPushH2StreamHandler.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerPushH2StreamHandler.java index e47cdb197e..e13bdf3769 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerPushH2StreamHandler.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerPushH2StreamHandler.java @@ -105,9 +105,9 @@ public void endStream() throws IOException { this.connMetrics = connMetrics; this.pushProducer = pushProducer; this.context = context; - this.responseCommitted = new AtomicBoolean(false); - this.failed = new AtomicBoolean(false); - this.done = new AtomicBoolean(false); + this.responseCommitted = new AtomicBoolean(); + this.failed = new AtomicBoolean(); + this.done = new AtomicBoolean(); this.requestState = MessageState.COMPLETE; this.responseState = MessageState.IDLE; } @@ -166,7 +166,7 @@ private void commitResponse( if (responseCommitted.compareAndSet(false, true)) { context.setProtocolVersion(HttpVersion.HTTP_2); - context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response); + context.setResponse(response); httpProcessor.process(response, responseEntityDetails, context); final List
headers = DefaultH2ResponseConverter.INSTANCE.convert(response); @@ -186,7 +186,7 @@ private void commitPromise( final AsyncPushProducer pushProducer) throws HttpException, IOException { context.setProtocolVersion(HttpVersion.HTTP_2); - context.setAttribute(HttpCoreContext.HTTP_REQUEST, promise); + context.setRequest(promise); httpProcessor.process(promise, null, context); final List
headers = DefaultH2RequestConverter.INSTANCE.convert(promise); diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/CancellableExecution.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/CancellableExecution.java index 84e89dc18b..3292371cdf 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/CancellableExecution.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/CancellableExecution.java @@ -38,7 +38,7 @@ final class CancellableExecution implements CancellableDependency { private final AtomicReference dependencyRef; CancellableExecution() { - this.cancelled = new AtomicBoolean(false); + this.cancelled = new AtomicBoolean(); this.dependencyRef = new AtomicReference<>(); } diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2MultiplexingRequester.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2MultiplexingRequester.java index d4edea96e6..b08eff9a87 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2MultiplexingRequester.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2MultiplexingRequester.java @@ -49,7 +49,6 @@ 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.ProtocolException; 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; @@ -119,7 +118,11 @@ 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, @@ -128,18 +131,38 @@ public Cancellable execute( Args.notNull(timeout, "Timeout"); Args.notNull(context, "Context"); final CancellableExecution cancellableExecution = new CancellableExecution(); - execute(exchangeHandler, pushHandlerFactory, cancellableExecution, timeout, context); + 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(exchangeHandler, null, timeout, 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, @@ -150,13 +173,11 @@ private void execute( Args.notNull(context, "Context"); try { exchangeHandler.produceRequest((request, entityDetails, httpContext) -> { - final String scheme = request.getScheme(); - final URIAuthority authority = request.getAuthority(); - if (authority == null) { - throw new ProtocolException("Request authority not specified"); + final HttpHost host = target != null ? target : defaultTarget(request); + if (request.getAuthority() == null) { + request.setAuthority(new URIAuthority(host.getHostName(), host.getPort())); } - final HttpHost target = new HttpHost(scheme, authority); - connPool.getSession(target, timeout, new FutureCallback() { + connPool.getSession(host, timeout, new FutureCallback() { @Override public void completed(final IOSession ioSession) { @@ -242,7 +263,12 @@ public void cancelled() { } } + /** + * @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, @@ -264,17 +290,41 @@ public void completed(final T result) { } }); - execute(exchangeHandler, pushHandlerFactory, future, timeout, context != null ? context : HttpCoreContext.create()); + 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(requestProducer, responseConsumer, null, timeout, context, callback); + return execute(null, requestProducer, responseConsumer, null, timeout, context, callback); } public final Future execute( @@ -282,7 +332,7 @@ public final Future execute( final AsyncResponseConsumer responseConsumer, final Timeout timeout, final FutureCallback callback) { - return execute(requestProducer, responseConsumer, null, timeout, null, callback); + return execute(null, requestProducer, responseConsumer, null, timeout, null, callback); } @Internal diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2MultiplexingRequesterBootstrap.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2MultiplexingRequesterBootstrap.java index 57cd21d63b..207bea1984 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2MultiplexingRequesterBootstrap.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2MultiplexingRequesterBootstrap.java @@ -34,15 +34,15 @@ 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.RequestHandlerRegistry; import org.apache.hc.core5.http.protocol.UriPatternType; import org.apache.hc.core5.http2.config.H2Config; 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.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; @@ -58,7 +58,7 @@ */ public class H2MultiplexingRequesterBootstrap { - private final List>> pushConsumerList; + private final List>> routeEntries; private UriPatternType uriPatternType; private IOReactorConfig ioReactorConfig; private HttpProcessor httpProcessor; @@ -72,7 +72,7 @@ public class H2MultiplexingRequesterBootstrap { private H2StreamListener streamListener; private H2MultiplexingRequesterBootstrap() { - this.pushConsumerList = new ArrayList<>(); + this.routeEntries = new ArrayList<>(); } public static H2MultiplexingRequesterBootstrap bootstrap() { @@ -81,6 +81,8 @@ public static H2MultiplexingRequesterBootstrap bootstrap() { /** * Sets I/O reactor configuration. + * + * @return this instance. */ public final H2MultiplexingRequesterBootstrap setIOReactorConfig(final IOReactorConfig ioReactorConfig) { this.ioReactorConfig = ioReactorConfig; @@ -88,7 +90,9 @@ public final H2MultiplexingRequesterBootstrap setIOReactorConfig(final IOReactor } /** - * Assigns {@link HttpProcessor} instance. + * Sets {@link HttpProcessor} instance. + * + * @return this instance. */ public final H2MultiplexingRequesterBootstrap setHttpProcessor(final HttpProcessor httpProcessor) { this.httpProcessor = httpProcessor; @@ -97,6 +101,8 @@ public final H2MultiplexingRequesterBootstrap setHttpProcessor(final HttpProcess /** * Sets HTTP/2 protocol parameters + * + * @return this instance. */ public final H2MultiplexingRequesterBootstrap setH2Config(final H2Config h2Config) { this.h2Config = h2Config; @@ -105,6 +111,8 @@ public final H2MultiplexingRequesterBootstrap setH2Config(final H2Config h2Confi /** * Sets message char coding. + * + * @return this instance. */ public final H2MultiplexingRequesterBootstrap setCharCodingConfig(final CharCodingConfig charCodingConfig) { this.charCodingConfig = charCodingConfig; @@ -112,7 +120,9 @@ public final H2MultiplexingRequesterBootstrap setCharCodingConfig(final CharCodi } /** - * Assigns {@link TlsStrategy} instance. + * Sets {@link TlsStrategy} instance. + * + * @return this instance. */ public final H2MultiplexingRequesterBootstrap setTlsStrategy(final TlsStrategy tlsStrategy) { this.tlsStrategy = tlsStrategy; @@ -125,7 +135,9 @@ public final H2MultiplexingRequesterBootstrap setStrictALPNHandshake(final boole } /** - * Assigns {@link IOSession} {@link Decorator} instance. + * Sets {@link IOSession} {@link Decorator} instance. + * + * @return this instance. */ public final H2MultiplexingRequesterBootstrap setIOSessionDecorator(final Decorator ioSessionDecorator) { this.ioSessionDecorator = ioSessionDecorator; @@ -133,7 +145,9 @@ public final H2MultiplexingRequesterBootstrap setIOSessionDecorator(final Decora } /** - * Assigns {@link Exception} {@link Callback} instance. + * Sets {@link Exception} {@link Callback} instance. + * + * @return this instance. */ public final H2MultiplexingRequesterBootstrap setExceptionCallback(final Callback exceptionCallback) { this.exceptionCallback = exceptionCallback; @@ -141,7 +155,9 @@ public final H2MultiplexingRequesterBootstrap setExceptionCallback(final Callbac } /** - * Assigns {@link IOSessionListener} instance. + * Sets {@link IOSessionListener} instance. + * + * @return this instance. */ public final H2MultiplexingRequesterBootstrap setIOSessionListener(final IOSessionListener sessionListener) { this.sessionListener = sessionListener; @@ -149,7 +165,9 @@ public final H2MultiplexingRequesterBootstrap setIOSessionListener(final IOSessi } /** - * Assigns {@link H2StreamListener} instance. + * Sets {@link H2StreamListener} instance. + * + * @return this instance. */ public final H2MultiplexingRequesterBootstrap setStreamListener(final H2StreamListener streamListener) { this.streamListener = streamListener; @@ -157,7 +175,9 @@ public final H2MultiplexingRequesterBootstrap setStreamListener(final H2StreamLi } /** - * Assigns {@link UriPatternType} for handler registration. + * Sets {@link UriPatternType} for handler registration. + * + * @return this instance. */ public final H2MultiplexingRequesterBootstrap setUriPatternType(final UriPatternType uriPatternType) { this.uriPatternType = uriPatternType; @@ -170,11 +190,12 @@ public final H2MultiplexingRequesterBootstrap setUriPatternType(final UriPattern * * @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, "Supplier"); - pushConsumerList.add(new HandlerEntry<>(null, uriPattern, supplier)); + Args.notNull(supplier, "Push consumer supplier"); + routeEntries.add(new RequestRouter.Entry<>(uriPattern, supplier)); return this; } @@ -185,23 +206,33 @@ public final H2MultiplexingRequesterBootstrap register(final String uriPattern, * @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 registerVirtual(final String hostname, final String uriPattern, final Supplier supplier) { + 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, "Supplier"); - pushConsumerList.add(new HandlerEntry<>(hostname, uriPattern, supplier)); + 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 RequestHandlerRegistry> registry = new RequestHandlerRegistry<>(uriPatternType); - for (final HandlerEntry> entry: pushConsumerList) { - registry.register(entry.hostname, entry.uriPattern, entry.handler); - } + 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(registry), + new DefaultAsyncPushConsumerFactory(requestRouter), h2Config != null ? h2Config : H2Config.DEFAULT, charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT, streamListener); diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2RequesterBootstrap.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2RequesterBootstrap.java index 8ea1490ef3..c0ea93a0d7 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2RequesterBootstrap.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2RequesterBootstrap.java @@ -43,10 +43,10 @@ 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.RequestHandlerRegistry; import org.apache.hc.core5.http.protocol.UriPatternType; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; @@ -78,7 +78,7 @@ */ public class H2RequesterBootstrap { - private final List>> pushConsumerList; + private final List>> routeEntries; private UriPatternType uriPatternType; private IOReactorConfig ioReactorConfig; private HttpProcessor httpProcessor; @@ -101,7 +101,7 @@ public class H2RequesterBootstrap { private ConnPoolListener connPoolListener; private H2RequesterBootstrap() { - this.pushConsumerList = new ArrayList<>(); + this.routeEntries = new ArrayList<>(); } public static H2RequesterBootstrap bootstrap() { @@ -110,6 +110,8 @@ public static H2RequesterBootstrap bootstrap() { /** * Sets I/O reactor configuration. + * + * @return this instance. */ public final H2RequesterBootstrap setIOReactorConfig(final IOReactorConfig ioReactorConfig) { this.ioReactorConfig = ioReactorConfig; @@ -117,7 +119,9 @@ public final H2RequesterBootstrap setIOReactorConfig(final IOReactorConfig ioRea } /** - * Assigns {@link HttpProcessor} instance. + * Sets {@link HttpProcessor} instance. + * + * @return this instance. */ public final H2RequesterBootstrap setHttpProcessor(final HttpProcessor httpProcessor) { this.httpProcessor = httpProcessor; @@ -126,6 +130,8 @@ public final H2RequesterBootstrap setHttpProcessor(final HttpProcessor httpProce /** * Sets HTTP protocol version policy + * + * @return this instance. */ public final H2RequesterBootstrap setVersionPolicy(final HttpVersionPolicy versionPolicy) { this.versionPolicy = versionPolicy; @@ -134,6 +140,8 @@ public final H2RequesterBootstrap setVersionPolicy(final HttpVersionPolicy versi /** * Sets HTTP/2 protocol parameters + * + * @return this instance. */ public final H2RequesterBootstrap setH2Config(final H2Config h2Config) { this.h2Config = h2Config; @@ -142,6 +150,8 @@ public final H2RequesterBootstrap setH2Config(final H2Config h2Config) { /** * Sets HTTP/1.1 protocol parameters + * + * @return this instance. */ public final H2RequesterBootstrap setHttp1Config(final Http1Config http1Config) { this.http1Config = http1Config; @@ -150,6 +160,8 @@ public final H2RequesterBootstrap setHttp1Config(final Http1Config http1Config) /** * Sets message char coding. + * + * @return this instance. */ public final H2RequesterBootstrap setCharCodingConfig(final CharCodingConfig charCodingConfig) { this.charCodingConfig = charCodingConfig; @@ -172,7 +184,9 @@ public final H2RequesterBootstrap setTimeToLive(final TimeValue timeToLive) { } /** - * Assigns {@link PoolReusePolicy} instance. + * Sets {@link PoolReusePolicy} instance. + * + * @return this instance. */ public final H2RequesterBootstrap setPoolReusePolicy(final PoolReusePolicy poolReusePolicy) { this.poolReusePolicy = poolReusePolicy; @@ -180,7 +194,9 @@ public final H2RequesterBootstrap setPoolReusePolicy(final PoolReusePolicy poolR } /** - * Assigns {@link PoolConcurrencyPolicy} instance. + * Sets {@link PoolConcurrencyPolicy} instance. + * + * @return this instance. */ @Experimental public final H2RequesterBootstrap setPoolConcurrencyPolicy(final PoolConcurrencyPolicy poolConcurrencyPolicy) { @@ -189,7 +205,9 @@ public final H2RequesterBootstrap setPoolConcurrencyPolicy(final PoolConcurrency } /** - * Assigns {@link TlsStrategy} instance. + * Sets {@link TlsStrategy} instance. + * + * @return this instance. */ public final H2RequesterBootstrap setTlsStrategy(final TlsStrategy tlsStrategy) { this.tlsStrategy = tlsStrategy; @@ -202,7 +220,9 @@ public final H2RequesterBootstrap setHandshakeTimeout(final Timeout handshakeTim } /** - * Assigns {@link IOSession} {@link Decorator} instance. + * Sets {@link IOSession} {@link Decorator} instance. + * + * @return this instance. */ public final H2RequesterBootstrap setIOSessionDecorator(final Decorator ioSessionDecorator) { this.ioSessionDecorator = ioSessionDecorator; @@ -210,7 +230,9 @@ public final H2RequesterBootstrap setIOSessionDecorator(final Decorator exceptionCallback) { this.exceptionCallback = exceptionCallback; @@ -218,7 +240,9 @@ public final H2RequesterBootstrap setExceptionCallback(final Callback } /** - * Assigns {@link IOSessionListener} instance. + * Sets {@link IOSessionListener} instance. + * + * @return this instance. */ public final H2RequesterBootstrap setIOSessionListener(final IOSessionListener sessionListener) { this.sessionListener = sessionListener; @@ -226,7 +250,9 @@ public final H2RequesterBootstrap setIOSessionListener(final IOSessionListener s } /** - * Assigns {@link H2StreamListener} instance. + * Sets {@link H2StreamListener} instance. + * + * @return this instance. */ public final H2RequesterBootstrap setStreamListener(final H2StreamListener streamListener) { this.streamListener = streamListener; @@ -234,7 +260,9 @@ public final H2RequesterBootstrap setStreamListener(final H2StreamListener strea } /** - * Assigns {@link Http1StreamListener} instance. + * Sets {@link Http1StreamListener} instance. + * + * @return this instance. */ public final H2RequesterBootstrap setStreamListener(final Http1StreamListener http1StreamListener) { this.http1StreamListener = http1StreamListener; @@ -242,7 +270,9 @@ public final H2RequesterBootstrap setStreamListener(final Http1StreamListener ht } /** - * Assigns {@link ConnPoolListener} instance. + * Sets {@link ConnPoolListener} instance. + * + * @return this instance. */ public final H2RequesterBootstrap setConnPoolListener(final ConnPoolListener connPoolListener) { this.connPoolListener = connPoolListener; @@ -250,7 +280,9 @@ public final H2RequesterBootstrap setConnPoolListener(final ConnPoolListener supplier) { Args.notBlank(uriPattern, "URI pattern"); - Args.notNull(supplier, "Supplier"); - pushConsumerList.add(new HandlerEntry<>(null, uriPattern, supplier)); + Args.notNull(supplier, "Push consumer supplier"); + routeEntries.add(new RequestRouter.Entry<>(uriPattern, supplier)); return this; } @@ -278,15 +311,27 @@ public final H2RequesterBootstrap register(final String uriPattern, final Suppli * @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 registerVirtual(final String hostname, final String uriPattern, final Supplier supplier) { + 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, "Supplier"); - pushConsumerList.add(new HandlerEntry<>(hostname, uriPattern, supplier)); + 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) { @@ -309,14 +354,12 @@ public H2AsyncRequester create() { connPoolListener); break; } - final RequestHandlerRegistry> registry = new RequestHandlerRegistry<>(uriPatternType); - for (final HandlerEntry> entry: pushConsumerList) { - registry.register(entry.hostname, entry.uriPattern, entry.handler); - } + 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(registry), + new DefaultAsyncPushConsumerFactory(requestRouter), h2Config != null ? h2Config : H2Config.DEFAULT, charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT, streamListener); diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2ServerBootstrap.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2ServerBootstrap.java index d60f842769..822cbe7f4a 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2ServerBootstrap.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2ServerBootstrap.java @@ -32,6 +32,7 @@ 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; @@ -44,6 +45,7 @@ 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; @@ -57,8 +59,6 @@ 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.LookupRegistry; -import org.apache.hc.core5.http.protocol.RequestHandlerRegistry; import org.apache.hc.core5.http.protocol.UriPatternType; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; @@ -68,6 +68,7 @@ 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.IOSession; @@ -80,12 +81,14 @@ * * @since 5.0 */ +@SuppressWarnings("deprecation") public class H2ServerBootstrap { - private final List>> handlerList; + private final List>> routeEntries; private final List> filters; private String canonicalHostName; - private LookupRegistry> lookupRegistry; + private org.apache.hc.core5.http.protocol.LookupRegistry> lookupRegistry; + private HttpRequestMapper> requestRouter; private IOReactorConfig ioReactorConfig; private HttpProcessor httpProcessor; private CharCodingConfig charCodingConfig; @@ -101,7 +104,7 @@ public class H2ServerBootstrap { private Http1StreamListener http1StreamListener; private H2ServerBootstrap() { - this.handlerList = new ArrayList<>(); + this.routeEntries = new ArrayList<>(); this.filters = new ArrayList<>(); } @@ -112,6 +115,7 @@ public static H2ServerBootstrap bootstrap() { /** * Sets canonical name (fully qualified domain name) of the server. * + * @return this instance. * @since 5.0 */ public final H2ServerBootstrap setCanonicalHostName(final String canonicalHostName) { @@ -121,6 +125,8 @@ public final H2ServerBootstrap setCanonicalHostName(final String canonicalHostNa /** * Sets I/O reactor configuration. + * + * @return this instance. */ public final H2ServerBootstrap setIOReactorConfig(final IOReactorConfig ioReactorConfig) { this.ioReactorConfig = ioReactorConfig; @@ -128,7 +134,9 @@ public final H2ServerBootstrap setIOReactorConfig(final IOReactorConfig ioReacto } /** - * Assigns {@link HttpProcessor} instance. + * Sets {@link HttpProcessor} instance. + * + * @return this instance. */ public final H2ServerBootstrap setHttpProcessor(final HttpProcessor httpProcessor) { this.httpProcessor = httpProcessor; @@ -137,6 +145,8 @@ public final H2ServerBootstrap setHttpProcessor(final HttpProcessor httpProcesso /** * Sets HTTP protocol version policy + * + * @return this instance. */ public final H2ServerBootstrap setVersionPolicy(final HttpVersionPolicy versionPolicy) { this.versionPolicy = versionPolicy; @@ -145,6 +155,8 @@ public final H2ServerBootstrap setVersionPolicy(final HttpVersionPolicy versionP /** * Sets HTTP/2 protocol parameters + * + * @return this instance. */ public final H2ServerBootstrap setH2Config(final H2Config h2Config) { this.h2Config = h2Config; @@ -153,6 +165,8 @@ public final H2ServerBootstrap setH2Config(final H2Config h2Config) { /** * Sets HTTP/1.1 protocol parameters + * + * @return this instance. */ public final H2ServerBootstrap setHttp1Config(final Http1Config http1Config) { this.http1Config = http1Config; @@ -161,6 +175,8 @@ public final H2ServerBootstrap setHttp1Config(final Http1Config http1Config) { /** * Sets message char coding. + * + * @return this instance. */ public final H2ServerBootstrap setCharset(final CharCodingConfig charCodingConfig) { this.charCodingConfig = charCodingConfig; @@ -168,7 +184,9 @@ public final H2ServerBootstrap setCharset(final CharCodingConfig charCodingConfi } /** - * Assigns {@link TlsStrategy} instance. + * Sets {@link TlsStrategy} instance. + * + * @return this instance. */ public final H2ServerBootstrap setTlsStrategy(final TlsStrategy tlsStrategy) { this.tlsStrategy = tlsStrategy; @@ -181,7 +199,9 @@ public final H2ServerBootstrap setHandshakeTimeout(final Timeout handshakeTimeou } /** - * Assigns {@link IOSession} {@link Decorator} instance. + * Sets {@link IOSession} {@link Decorator} instance. + * + * @return this instance. */ public final H2ServerBootstrap setIOSessionDecorator(final Decorator ioSessionDecorator) { this.ioSessionDecorator = ioSessionDecorator; @@ -189,7 +209,9 @@ public final H2ServerBootstrap setIOSessionDecorator(final Decorator } /** - * Assigns {@link Exception} {@link Callback} instance. + * Sets {@link Exception} {@link Callback} instance. + * + * @return this instance. */ public final H2ServerBootstrap setExceptionCallback(final Callback exceptionCallback) { this.exceptionCallback = exceptionCallback; @@ -197,7 +219,9 @@ public final H2ServerBootstrap setExceptionCallback(final Callback ex } /** - * Assigns {@link IOSessionListener} instance. + * Sets {@link IOSessionListener} instance. + * + * @return this instance. */ public final H2ServerBootstrap setIOSessionListener(final IOSessionListener sessionListener) { this.sessionListener = sessionListener; @@ -205,7 +229,9 @@ public final H2ServerBootstrap setIOSessionListener(final IOSessionListener sess } /** - * Assigns {@link H2StreamListener} instance. + * Sets {@link H2StreamListener} instance. + * + * @return this instance. */ public final H2ServerBootstrap setStreamListener(final H2StreamListener h2StreamListener) { this.h2StreamListener = h2StreamListener; @@ -213,7 +239,9 @@ public final H2ServerBootstrap setStreamListener(final H2StreamListener h2Stream } /** - * Assigns {@link Http1StreamListener} instance. + * Sets {@link Http1StreamListener} instance. + * + * @return this instance. */ public final H2ServerBootstrap setStreamListener(final Http1StreamListener http1StreamListener) { this.http1StreamListener = http1StreamListener; @@ -221,24 +249,39 @@ public final H2ServerBootstrap setStreamListener(final Http1StreamListener http1 } /** - * Assigns {@link LookupRegistry} instance. + * @return this instance. + * @deprecated Use {@link RequestRouter}. */ - public final H2ServerBootstrap setLookupRegistry(final LookupRegistry> lookupRegistry) { + @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, "Supplier"); - handlerList.add(new HandlerEntry<>(null, uriPattern, supplier)); + Args.notNull(supplier, "Exchange handler supplier"); + routeEntries.add(new RequestRouter.Entry<>(uriPattern, supplier)); return this; } @@ -249,21 +292,35 @@ public final H2ServerBootstrap register(final String uriPattern, final Supplier< * @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 registerVirtual(final String hostname, final String uriPattern, final Supplier supplier) { + 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, "Supplier"); - handlerList.add(new HandlerEntry<>(hostname, uriPattern, supplier)); + 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, @@ -276,11 +333,15 @@ public final H2ServerBootstrap register( * 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 registerVirtual( + public final H2ServerBootstrap register( final String hostname, final String uriPattern, final AsyncServerRequestHandler requestHandler) { @@ -288,8 +349,23 @@ public final H2ServerBootstrap registerVirtual( 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"); @@ -301,6 +377,8 @@ public final H2ServerBootstrap addFilterBefore(final String existing, final Stri /** * 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"); @@ -312,6 +390,8 @@ public final H2ServerBootstrap addFilterAfter(final String existing, final Strin /** * 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"); @@ -322,6 +402,8 @@ public final H2ServerBootstrap replaceFilter(final String existing, final AsyncF /** * 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"); @@ -332,6 +414,8 @@ public final H2ServerBootstrap addFilterFirst(final String name, final AsyncFilt /** * 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"); @@ -342,19 +426,33 @@ public final H2ServerBootstrap addFilterLast(final String name, final AsyncFilte public HttpAsyncServer create() { final String actualCanonicalHostName = canonicalHostName != null ? canonicalHostName : InetAddressUtils.getCanonicalLocalHostName(); - final RequestHandlerRegistry> registry = new RequestHandlerRegistry<>( - actualCanonicalHostName, - () -> lookupRegistry != null ? lookupRegistry : - UriPatternType.newMatcher(UriPatternType.URI_PATTERN)); - for (final HandlerEntry> entry: handlerList) { - registry.register(entry.hostname, entry.uriPattern, entry.handler); + 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(registry)), + new TerminalAsyncServerFilter(new DefaultAsyncResponseExchangeHandlerFactory(requestRouterCopy)), StandardFilter.MAIN_HANDLER.name()); filterChainDefinition.addFirst( new AsyncServerExpectationFilter(), @@ -391,7 +489,8 @@ public HttpAsyncServer create() { handlerFactory = new AsyncServerFilterChainExchangeHandlerFactory(execChain, exceptionCallback); } else { - handlerFactory = new DefaultAsyncResponseExchangeHandlerFactory(registry, handler -> new BasicAsyncServerExpectationDecorator(handler, exceptionCallback)); + handlerFactory = new DefaultAsyncResponseExchangeHandlerFactory(requestRouterCopy, + handler -> new BasicAsyncServerExpectationDecorator(handler, exceptionCallback)); } final ServerH2StreamMultiplexerFactory http2StreamHandlerFactory = new ServerH2StreamMultiplexerFactory( @@ -409,8 +508,8 @@ public HttpAsyncServer create() { http1Config != null ? http1Config : Http1Config.DEFAULT, charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT, DefaultConnectionReuseStrategy.INSTANCE, - DefaultHttpRequestParserFactory.INSTANCE, - DefaultHttpResponseWriterFactory.INSTANCE, + new DefaultHttpRequestParserFactory(http1Config), + new DefaultHttpResponseWriterFactory(http1Config), DefaultContentLengthStrategy.INSTANCE, DefaultContentLengthStrategy.INSTANCE, http1StreamListener); diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/nio/AsyncPingHandler.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/nio/AsyncPingHandler.java index cf31bf565b..9978170a26 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/nio/AsyncPingHandler.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/nio/AsyncPingHandler.java @@ -49,6 +49,8 @@ public interface AsyncPingHandler { * 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; diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestConnControl.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestConnControl.java index 9abf1fe0c0..9d0e66cd52 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestConnControl.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestConnControl.java @@ -41,6 +41,10 @@ /** * 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 */ diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestContent.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestContent.java index 064e9b21fe..07936b78a6 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestContent.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestContent.java @@ -44,6 +44,10 @@ /** * 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 */ @@ -82,6 +86,9 @@ public void process( 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); } } } diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestTargetHost.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestTargetHost.java index 8f7ea2d2cd..65116747a8 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestTargetHost.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestTargetHost.java @@ -41,6 +41,10 @@ /** * 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 */ diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestValidateHost.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestValidateHost.java index 76983a6ce9..c704e7bfb8 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestValidateHost.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestValidateHost.java @@ -41,6 +41,10 @@ /** * 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 */ diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2ResponseConnControl.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2ResponseConnControl.java index f9a1062781..7c4ed84747 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2ResponseConnControl.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2ResponseConnControl.java @@ -41,6 +41,10 @@ /** * 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 */ diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2ResponseContent.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2ResponseContent.java index 2670870fb4..e9b188d1ff 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2ResponseContent.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2ResponseContent.java @@ -42,6 +42,10 @@ /** * 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 */ diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/ApplicationProtocol.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/ApplicationProtocol.java index c4f55ab7bb..d55cf6128e 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/ApplicationProtocol.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/ApplicationProtocol.java @@ -34,8 +34,19 @@ */ public enum ApplicationProtocol { - HTTP_2("h2"), HTTP_1_1("http/1.1"); + /** + * 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) { diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/H2ClientTlsStrategy.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/H2ClientTlsStrategy.java index dea438b930..550c2fca6b 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/H2ClientTlsStrategy.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/H2ClientTlsStrategy.java @@ -30,6 +30,7 @@ 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; @@ -110,7 +111,14 @@ public void upgrade( sslContext, endpoint, sslBufferMode, - H2TlsSupport.enforceRequirements(attachment, initializer), + (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); diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/H2TlsSupport.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/H2TlsSupport.java index 3cf0263add..735d13f179 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/H2TlsSupport.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/ssl/H2TlsSupport.java @@ -67,16 +67,25 @@ public static String[] selectApplicationProtocols(final Object attachment) { } } + /** + * @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) -> { - final SSLParameters sslParameters = sslEngine.getSSLParameters(); - sslParameters.setProtocols(TLS.excludeWeak(sslParameters.getProtocols())); - sslParameters.setCipherSuites(TlsCiphers.excludeH2Blacklisted(sslParameters.getCipherSuites())); - setEnableRetransmissions(sslParameters, false); - sslParameters.setApplicationProtocols(selectApplicationProtocols(attachment)); - sslEngine.setSSLParameters(sslParameters); + sslEngine.setSSLParameters(enforceRequirements(attachment, sslEngine.getSSLParameters())); if (initializer != null) { initializer.initialize(endpoint, sslEngine); } diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/config/H2ConfigTest.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/config/H2ConfigTest.java index 39e2b9d21b..a6a2859d5f 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/config/H2ConfigTest.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/config/H2ConfigTest.java @@ -34,7 +34,7 @@ import org.junit.jupiter.api.Test; -public class H2ConfigTest { +class H2ConfigTest { @Test void builder() { diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2FileServerExample.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2FileServerExample.java index c4d24f0e8d..91bfc10cef 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2FileServerExample.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2FileServerExample.java @@ -137,7 +137,8 @@ public AsyncRequestConsumer> prepare( public void handle( final Message message, final ResponseTrigger responseTrigger, - final HttpContext context) throws HttpException, IOException { + final HttpContext localContext) throws HttpException, IOException { + final HttpCoreContext context = HttpCoreContext.cast(localContext); final HttpRequest request = message.getHead(); final URI requestUri; try { @@ -180,8 +181,7 @@ public void handle( contentType = ContentType.DEFAULT_BINARY; } - final HttpCoreContext coreContext = HttpCoreContext.adapt(context); - final EndpointDetails endpoint = coreContext.getEndpointDetails(); + final EndpointDetails endpoint = context.getEndpointDetails(); System.out.println(endpoint + ": serving file " + file.getPath()); responseTrigger.submitResponse( AsyncResponseBuilder.create(HttpStatus.SC_OK) diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2GreetingServer.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2GreetingServer.java index fcb83ee9f2..2918e9d595 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2GreetingServer.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2GreetingServer.java @@ -76,7 +76,7 @@ * 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=ISO-8859-1 + * Content-Type: text/plain; charset=UTF-8 * * Hello bob * } @@ -134,10 +134,10 @@ protected AsyncRequestConsumer> supplyConsumer( @Override protected void handle(final Message requestMessage, final AsyncServerRequestHandler.ResponseTrigger responseTrigger, - final HttpContext context) throws HttpException, IOException { + final HttpContext localContext) throws HttpException, IOException { - final HttpCoreContext coreContext = HttpCoreContext.adapt(context); - final EndpointDetails endpoint = coreContext.getEndpointDetails(); + final HttpCoreContext context = HttpCoreContext.cast(localContext); + final EndpointDetails endpoint = context.getEndpointDetails(); final HttpRequest req = requestMessage.getHead(); final String httpEntity = requestMessage.getBody(); @@ -146,7 +146,7 @@ protected void handle(final Message requestMessage, // recording the request System.out.printf("[%s] %s %s %s%n", Instant.now(), - endpoint.getRemoteAddress(), + endpoint != null ? endpoint.getRemoteAddress() : null, req.getMethod(), req.getPath()); diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestDefaultFrameFactory.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestDefaultFrameFactory.java index 676744236e..f5cbe857b8 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestDefaultFrameFactory.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestDefaultFrameFactory.java @@ -35,10 +35,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestDefaultFrameFactory { +class TestDefaultFrameFactory { @Test - public void testDataFrame() throws Exception { + void testDataFrame() { final FrameFactory frameFactory = new DefaultFrameFactory(); @@ -51,7 +51,7 @@ public void testDataFrame() throws Exception { } @Test - public void testSettingFrame() throws Exception { + void testSettingFrame() { final FrameFactory frameFactory = new DefaultFrameFactory(); final Frame settingsFrame = frameFactory.createSettings( @@ -67,7 +67,7 @@ public void testSettingFrame() throws Exception { } @Test - public void testResetStreamFrame() throws Exception { + void testResetStreamFrame() { final FrameFactory frameFactory = new DefaultFrameFactory(); final Frame rstStreamFrame = frameFactory.createResetStream(12, H2Error.INTERNAL_ERROR); @@ -82,7 +82,7 @@ public void testResetStreamFrame() throws Exception { } @Test - public void testGoAwayFrame() throws Exception { + void testGoAwayFrame() { final FrameFactory frameFactory = new DefaultFrameFactory(); final Frame goAwayFrame = frameFactory.createGoAway(13, H2Error.INTERNAL_ERROR, "Oopsie"); diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestFrameFlag.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestFrameFlag.java index 56235360ae..62a77505eb 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestFrameFlag.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestFrameFlag.java @@ -29,10 +29,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestFrameFlag { +class TestFrameFlag { @Test - public void testFrameFlagBasics() throws Exception { + void testFrameFlagBasics() { final int flags = FrameFlag.of(FrameFlag.END_STREAM, FrameFlag.PADDED, FrameFlag.PRIORITY); Assertions.assertEquals(0x01 | 0x08 | 0x20, flags); diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestH2Settings.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestH2Settings.java index 74d446f107..c8486708dd 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestH2Settings.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestH2Settings.java @@ -31,10 +31,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestH2Settings { +class TestH2Settings { @Test - public void testH2ParamBasics() throws Exception { + void testH2ParamBasics() { for (final H2Param param: H2Param.values()) { Assertions.assertEquals(param, H2Param.valueOf(param.getCode())); Assertions.assertEquals(param.name(), H2Param.toString(param.getCode())); @@ -46,7 +46,7 @@ public void testH2ParamBasics() throws Exception { } @Test - public void testH2SettingBasics() throws Exception { + void testH2SettingBasics() { final H2Setting setting1 = new H2Setting(H2Param.ENABLE_PUSH, 0); final H2Setting setting2 = new H2Setting(H2Param.INITIAL_WINDOW_SIZE, 1024); diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestFifoBuffer.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestFifoBuffer.java index fbf365e6e9..172db8419c 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestFifoBuffer.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestFifoBuffer.java @@ -30,10 +30,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestFifoBuffer { +class TestFifoBuffer { @Test - public void testAddRemoveCycle() throws Exception { + void testAddRemoveCycle() { final FifoBuffer fifoBuffer = new FifoBuffer(5); @@ -88,7 +88,7 @@ public void testAddRemoveCycle() throws Exception { } @Test - public void testExpand() throws Exception { + void testExpand() { final HPackHeader h1 = new HPackHeader("h", "1"); final HPackHeader h2 = new HPackHeader("h", "2"); diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestFifoLinkedList.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestFifoLinkedList.java index ea31a5818a..472d8189ba 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestFifoLinkedList.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestFifoLinkedList.java @@ -30,10 +30,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestFifoLinkedList { +class TestFifoLinkedList { @Test - public void testAddRemoveCycle() throws Exception { + void testAddRemoveCycle() { final FifoLinkedList fifoLinkedList = new FifoLinkedList(); @@ -90,7 +90,7 @@ public void testAddRemoveCycle() throws Exception { } @Test - public void testGetIndex() throws Exception { + void testGetIndex() { final FifoLinkedList fifoLinkedList = new FifoLinkedList(); diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestHPackCoding.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestHPackCoding.java index 47dc1ce7fb..dba042bd29 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestHPackCoding.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestHPackCoding.java @@ -42,10 +42,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestHPackCoding { +class TestHPackCoding { @Test - public void testIntegerEncodingRFC7541Examples() throws Exception { + void testIntegerEncodingRFC7541Examples() { final ByteArrayBuffer buffer = new ByteArrayBuffer(16); HPackEncoder.encodeInt(buffer, 5, 10, 0x0); @@ -82,7 +82,7 @@ private static byte[] toArray(final ByteBuffer buffer) { } @Test - public void testIntegerCoding() throws Exception { + void testIntegerCoding() throws Exception { final ByteArrayBuffer buffer = new ByteArrayBuffer(16); @@ -107,7 +107,7 @@ public void testIntegerCoding() throws Exception { } @Test - public void testIntegerCodingLimit() throws Exception { + void testIntegerCodingLimit() throws Exception { final ByteBuffer src1 = createByteBuffer(0x7f, 0x80, 0xff, 0xff, 0xff, 0x07); Assertions.assertEquals(Integer.MAX_VALUE, HPackDecoder.decodeInt(src1, 7)); @@ -135,7 +135,7 @@ private static ByteBuffer createByteBuffer(final int... bytes) { } @Test - public void testPlainStringDecoding() throws Exception { + void testPlainStringDecoding() throws Exception { final ByteBuffer src = createByteBuffer( 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79); @@ -147,19 +147,19 @@ public void testPlainStringDecoding() throws Exception { } @Test - public void testPlainStringDecodingRemainingContent() throws Exception { + 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(new String(buffer.array(), 0, buffer.length(), StandardCharsets.US_ASCII), "custom-key"); + Assertions.assertEquals("custom-key", new String(buffer.array(), 0, buffer.length(), StandardCharsets.US_ASCII)); Assertions.assertEquals(4, src.remaining()); } @Test - public void testPlainStringDecodingReadOnly() throws Exception { + void testPlainStringDecodingReadOnly() throws Exception { final ByteBuffer src = createByteBuffer( 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x50, 0x50, 0x50, 0x50); @@ -172,7 +172,7 @@ public void testPlainStringDecodingReadOnly() throws Exception { } @Test - public void testPlainStringDecodingTruncated() throws Exception { + void testPlainStringDecodingTruncated() { final ByteBuffer src = createByteBuffer( 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65); @@ -182,13 +182,14 @@ public void testPlainStringDecodingTruncated() throws Exception { } @Test - public void testHuffmanDecodingRFC7541Examples() throws Exception { + 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(new String(buffer.array(), 0, buffer.length(), StandardCharsets.US_ASCII), "www.example.com"); + Assertions.assertEquals("www.example.com", + new String(buffer.array(), 0, buffer.length(), StandardCharsets.US_ASCII)); Assertions.assertFalse(src.hasRemaining(), "Decoding completed"); } @@ -198,7 +199,7 @@ private static ByteBuffer createByteBuffer(final String s, final Charset charset } @Test - public void testHuffmanEncoding() throws Exception { + void testHuffmanEncoding() { final ByteArrayBuffer buffer = new ByteArrayBuffer(16); HPackEncoder.encodeHuffman(buffer, createByteBuffer("www.example.com", StandardCharsets.US_ASCII)); final ByteBuffer expected = createByteBuffer( @@ -207,7 +208,7 @@ public void testHuffmanEncoding() throws Exception { } @Test - public void testBasicStringCoding() throws Exception { + void testBasicStringCoding() throws Exception { final HPackEncoder encoder = new HPackEncoder(StandardCharsets.US_ASCII); final HPackDecoder decoder = new HPackDecoder(StandardCharsets.US_ASCII); @@ -226,6 +227,23 @@ public void testBasicStringCoding() throws Exception { Assertions.assertEquals("this and that and Huffman", strBuf.toString()); } + @Test + void testEnsureCapacity() throws Exception { + + final HPackEncoder encoder = new HPackEncoder(StandardCharsets.US_ASCII); + final HPackDecoder decoder = new HPackDecoder(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 }; @@ -249,7 +267,7 @@ private static String constructHelloString(final int[] raw, final int n) { } @Test - public void testComplexStringCoding1() throws Exception { + void testComplexStringCoding1() throws Exception { for (final Charset charset : new Charset[]{StandardCharsets.ISO_8859_1, StandardCharsets.UTF_8, StandardCharsets.UTF_16}) { @@ -277,7 +295,7 @@ public void testComplexStringCoding1() throws Exception { } @Test - public void testComplexStringCoding2() throws Exception { + void testComplexStringCoding2() throws Exception { for (final Charset charset : new Charset[]{Charset.forName("KOI8-R"), StandardCharsets.UTF_8, StandardCharsets.UTF_16}) { @@ -313,7 +331,7 @@ private static void assertHeaderEquals(final Header expected, final Header actua } @Test - public void testLiteralHeaderWithIndexingDecodingRFC7541Examples() throws Exception { + void testLiteralHeaderWithIndexingDecodingRFC7541Examples() throws Exception { final ByteBuffer src = createByteBuffer( 0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0d, 0x63, 0x75, 0x73, @@ -330,7 +348,7 @@ public void testLiteralHeaderWithIndexingDecodingRFC7541Examples() throws Except } @Test - public void testLiteralHeaderWithoutIndexingDecodingRFC7541Examples() throws Exception { + void testLiteralHeaderWithoutIndexingDecodingRFC7541Examples() throws Exception { final ByteBuffer src = createByteBuffer( 0x04, 0x0c, 0x2f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68); @@ -345,7 +363,7 @@ public void testLiteralHeaderWithoutIndexingDecodingRFC7541Examples() throws Exc } @Test - public void testLiteralHeaderNeverIndexedDecodingRFC7541Examples() throws Exception { + 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); @@ -360,7 +378,7 @@ public void testLiteralHeaderNeverIndexedDecodingRFC7541Examples() throws Except } @Test - public void testIndexedHeaderDecodingRFC7541Examples() throws Exception { + void testIndexedHeaderDecodingRFC7541Examples() throws Exception { final ByteBuffer src = createByteBuffer(0x82); @@ -374,7 +392,7 @@ public void testIndexedHeaderDecodingRFC7541Examples() throws Exception { } @Test - public void testRequestDecodingWithoutHuffmanRFC7541Examples() throws Exception { + 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, @@ -432,7 +450,7 @@ public void testRequestDecodingWithoutHuffmanRFC7541Examples() throws Exception } @Test - public void testRequestDecodingWithHuffmanRFC7541Examples() throws Exception { + 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); @@ -489,7 +507,7 @@ public void testRequestDecodingWithHuffmanRFC7541Examples() throws Exception { } @Test - public void testResponseDecodingWithoutHuffmanRFC7541Examples() throws Exception { + 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, @@ -562,7 +580,7 @@ public void testResponseDecodingWithoutHuffmanRFC7541Examples() throws Exception } @Test - public void testResponseDecodingWithHuffmanRFC7541Examples() throws Exception { + 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, @@ -641,7 +659,7 @@ private static byte[] createByteArray(final int... bytes) { } @Test - public void testLiteralHeaderWithIndexingEncodingRFC7541Examples() throws Exception { + void testLiteralHeaderWithIndexingEncodingRFC7541Examples() throws Exception { final OutboundDynamicTable dynamicTable = new OutboundDynamicTable(); final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII); @@ -659,7 +677,7 @@ public void testLiteralHeaderWithIndexingEncodingRFC7541Examples() throws Except } @Test - public void testLiteralHeaderWithoutIndexingEncodingRFC7541Examples() throws Exception { + void testLiteralHeaderWithoutIndexingEncodingRFC7541Examples() throws Exception { final OutboundDynamicTable dynamicTable = new OutboundDynamicTable(); final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII); @@ -685,7 +703,7 @@ public HPackHeader getHeader() { } @Test - public void testLiteralHeaderNeverIndexedEncodingRFC7541Examples() throws Exception { + void testLiteralHeaderNeverIndexedEncodingRFC7541Examples() throws Exception { final OutboundDynamicTable dynamicTable = new OutboundDynamicTable(); final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII); @@ -701,7 +719,7 @@ public void testLiteralHeaderNeverIndexedEncodingRFC7541Examples() throws Except } @Test - public void testIndexedHeaderEncodingRFC7541Examples() throws Exception { + void testIndexedHeaderEncodingRFC7541Examples() { final OutboundDynamicTable dynamicTable = new OutboundDynamicTable(); final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII); @@ -714,7 +732,7 @@ public void testIndexedHeaderEncodingRFC7541Examples() throws Exception { } @Test - public void testRequestEncodingWithoutHuffmanRFC7541Examples() throws Exception { + void testRequestEncodingWithoutHuffmanRFC7541Examples() throws Exception { final OutboundDynamicTable dynamicTable = new OutboundDynamicTable(); final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII); @@ -779,7 +797,7 @@ public void testRequestEncodingWithoutHuffmanRFC7541Examples() throws Exception } @Test - public void testRequestEncodingWithHuffmanRFC7541Examples() throws Exception { + void testRequestEncodingWithHuffmanRFC7541Examples() throws Exception { final OutboundDynamicTable dynamicTable = new OutboundDynamicTable(); final HPackEncoder encoder = new HPackEncoder(dynamicTable, StandardCharsets.US_ASCII); @@ -843,7 +861,7 @@ public void testRequestEncodingWithHuffmanRFC7541Examples() throws Exception { } @Test - public void testResponseEncodingWithoutHuffmanRFC7541Examples() throws Exception { + void testResponseEncodingWithoutHuffmanRFC7541Examples() throws Exception { final OutboundDynamicTable dynamicTable = new OutboundDynamicTable(); dynamicTable.setMaxSize(256); @@ -923,7 +941,7 @@ public void testResponseEncodingWithoutHuffmanRFC7541Examples() throws Exception } @Test - public void testResponseEncodingWithHuffmanRFC7541Examples() throws Exception { + void testResponseEncodingWithHuffmanRFC7541Examples() throws Exception { final OutboundDynamicTable dynamicTable = new OutboundDynamicTable(); dynamicTable.setMaxSize(256); @@ -1001,7 +1019,7 @@ public void testResponseEncodingWithHuffmanRFC7541Examples() throws Exception { } @Test - public void testHeaderEntrySizeNonAscii() throws Exception { + void testHeaderEntrySizeNonAscii() throws Exception { final ByteArrayBuffer buffer = new ByteArrayBuffer(128); final Header header = new BasicHeader("hello", constructHelloString(SWISS_GERMAN_HELLO, 1)); @@ -1041,7 +1059,7 @@ public void testHeaderEntrySizeNonAscii() throws Exception { } @Test - public void testHeaderSizeLimit() throws Exception { + void testHeaderSizeLimit() throws Exception { final HPackEncoder encoder = new HPackEncoder(StandardCharsets.US_ASCII); final HPackDecoder decoder = new HPackDecoder(StandardCharsets.US_ASCII); @@ -1066,7 +1084,7 @@ public void testHeaderSizeLimit() throws Exception { } @Test - public void testHeaderEmptyASCII() throws Exception { + void testHeaderEmptyASCII() throws Exception { final HPackEncoder encoder = new HPackEncoder(StandardCharsets.US_ASCII); final HPackDecoder decoder = new HPackDecoder(StandardCharsets.US_ASCII); @@ -1080,7 +1098,7 @@ public void testHeaderEmptyASCII() throws Exception { } @Test - public void testHeaderEmptyUTF8() throws Exception { + void testHeaderEmptyUTF8() throws Exception { final HPackEncoder encoder = new HPackEncoder(StandardCharsets.UTF_8); final HPackDecoder decoder = new HPackDecoder(StandardCharsets.UTF_8); diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestInboundDynamicTable.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestInboundDynamicTable.java index 38039acdee..0eb5ae9c60 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestInboundDynamicTable.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestInboundDynamicTable.java @@ -30,10 +30,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestInboundDynamicTable { +class TestInboundDynamicTable { @Test - public void testBasics() throws Exception { + void testBasics() { final InboundDynamicTable table = new InboundDynamicTable(); Assertions.assertEquals(Integer.MAX_VALUE, table.getMaxSize()); @@ -49,7 +49,7 @@ public void testBasics() throws Exception { } @Test - public void testEviction() throws Exception { + void testEviction() { final InboundDynamicTable table = new InboundDynamicTable(); diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestOutboundDynamicTable.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestOutboundDynamicTable.java index ec2649ba1f..b60bcb13dc 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestOutboundDynamicTable.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestOutboundDynamicTable.java @@ -30,10 +30,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestOutboundDynamicTable { +class TestOutboundDynamicTable { @Test - public void testBasics() throws Exception { + void testBasics() { final OutboundDynamicTable table = new OutboundDynamicTable(); Assertions.assertEquals(Integer.MAX_VALUE, table.getMaxSize()); @@ -49,7 +49,7 @@ public void testBasics() throws Exception { } @Test - public void testEviction() throws Exception { + void testEviction() { final OutboundDynamicTable table = new OutboundDynamicTable(); diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestDefaultH2RequestConverter.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestDefaultH2RequestConverter.java index 1caf966ff0..f985dcf187 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestDefaultH2RequestConverter.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestDefaultH2RequestConverter.java @@ -34,16 +34,17 @@ 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; -public class TestDefaultH2RequestConverter { +class TestDefaultH2RequestConverter { @Test - public void testConvertFromFieldsBasic() throws Exception { + void testConvertFromFieldsBasic() throws Exception { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), @@ -66,7 +67,7 @@ public void testConvertFromFieldsBasic() throws Exception { } @Test - public void testConvertFromFieldsUpperCaseHeaderName() throws Exception { + void testConvertFromFieldsUpperCaseHeaderName() { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), @@ -80,7 +81,7 @@ public void testConvertFromFieldsUpperCaseHeaderName() throws Exception { } @Test - public void testConvertFromFieldsConnectionHeader() throws Exception { + void testConvertFromFieldsConnectionHeader() { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), @@ -94,7 +95,7 @@ public void testConvertFromFieldsConnectionHeader() throws Exception { } @Test - public void testConvertFromFieldsPseudoHeaderSequence() throws Exception { + void testConvertFromFieldsPseudoHeaderSequence() { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), @@ -108,7 +109,7 @@ public void testConvertFromFieldsPseudoHeaderSequence() throws Exception { } @Test - public void testConvertFromFieldsMissingMethod() throws Exception { + void testConvertFromFieldsMissingMethod() { final List
headers = Arrays.asList( new BasicHeader(":scheme", "http"), new BasicHeader(":authority", "www.example.com"), @@ -121,7 +122,7 @@ public void testConvertFromFieldsMissingMethod() throws Exception { } @Test - public void testConvertFromFieldsMissingScheme() throws Exception { + void testConvertFromFieldsMissingScheme() { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":authority", "www.example.com"), @@ -134,7 +135,7 @@ public void testConvertFromFieldsMissingScheme() throws Exception { } @Test - public void testConvertFromFieldsMissingPath() throws Exception { + void testConvertFromFieldsMissingPath() { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), @@ -147,7 +148,7 @@ public void testConvertFromFieldsMissingPath() throws Exception { } @Test - public void testConvertFromFieldsUnknownPseudoHeader() throws Exception { + void testConvertFromFieldsUnknownPseudoHeader() { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), @@ -161,7 +162,7 @@ public void testConvertFromFieldsUnknownPseudoHeader() throws Exception { } @Test - public void testConvertFromFieldsMultipleMethod() throws Exception { + void testConvertFromFieldsMultipleMethod() { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":method", "GET"), @@ -176,7 +177,7 @@ public void testConvertFromFieldsMultipleMethod() throws Exception { } @Test - public void testConvertFromFieldsMultipleScheme() throws Exception { + void testConvertFromFieldsMultipleScheme() { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "http"), @@ -191,7 +192,7 @@ public void testConvertFromFieldsMultipleScheme() throws Exception { } @Test - public void testConvertFromFieldsMultiplePath() throws Exception { + void testConvertFromFieldsMultiplePath() { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), new BasicHeader(":scheme", "https"), @@ -206,7 +207,7 @@ public void testConvertFromFieldsMultiplePath() throws Exception { } @Test - public void testConvertFromFieldsConnect() throws Exception { + void testConvertFromFieldsConnect() throws Exception { final List
headers = Arrays.asList( new BasicHeader(":method", "CONNECT"), @@ -218,7 +219,7 @@ public void testConvertFromFieldsConnect() throws Exception { } @Test - public void testConvertFromFieldsConnectMissingAuthority() throws Exception { + void testConvertFromFieldsConnectMissingAuthority() { final List
headers = Arrays.asList( new BasicHeader(":method", "CONNECT"), new BasicHeader("custom", "value")); @@ -229,7 +230,7 @@ public void testConvertFromFieldsConnectMissingAuthority() throws Exception { } @Test - public void testConvertFromFieldsConnectPresentScheme() throws Exception { + void testConvertFromFieldsConnectPresentScheme() { final List
headers = Arrays.asList( new BasicHeader(":method", "CONNECT"), new BasicHeader(":scheme", "http"), @@ -242,7 +243,7 @@ public void testConvertFromFieldsConnectPresentScheme() throws Exception { } @Test - public void testConvertFromFieldsConnectPresentPath() throws Exception { + void testConvertFromFieldsConnectPresentPath() { final List
headers = Arrays.asList( new BasicHeader(":method", "CONNECT"), new BasicHeader(":authority", "www.example.com"), @@ -255,7 +256,7 @@ public void testConvertFromFieldsConnectPresentPath() throws Exception { } @Test - public void testConvertFromMessageBasic() throws Exception { + void testConvertFromMessageBasic() throws Exception { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("custom123", "Value"); @@ -283,7 +284,7 @@ public void testConvertFromMessageBasic() throws Exception { } @Test - public void testConvertFromMessageMissingScheme() throws Exception { + void testConvertFromMessageMissingScheme() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("Custom123", "Value"); request.setScheme(null); @@ -293,7 +294,7 @@ public void testConvertFromMessageMissingScheme() throws Exception { } @Test - public void testConvertFromMessageMissingPath() throws Exception { + void testConvertFromMessageMissingPath() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("Custom123", "Value"); request.setPath(null); @@ -303,7 +304,7 @@ public void testConvertFromMessageMissingPath() throws Exception { } @Test - public void testConvertFromMessageConnect() throws Exception { + void testConvertFromMessageConnect() throws Exception { final HttpRequest request = new BasicHttpRequest("CONNECT", new HttpHost("host:80"), null); request.addHeader("custom123", "Value"); @@ -325,7 +326,7 @@ public void testConvertFromMessageConnect() throws Exception { } @Test - public void testConvertFromMessageConnectMissingAuthority() throws Exception { + void testConvertFromMessageConnectMissingAuthority() { final HttpRequest request = new BasicHttpRequest("CONNECT", null, null); request.addHeader("Custom123", "Value"); @@ -335,7 +336,7 @@ public void testConvertFromMessageConnectMissingAuthority() throws Exception { } @Test - public void testConvertFromMessageConnectWithPath() throws Exception { + void testConvertFromMessageConnectWithPath() { final HttpRequest request = new BasicHttpRequest("CONNECT", "/"); request.setAuthority(new URIAuthority("host")); request.addHeader("Custom123", "Value"); @@ -346,7 +347,7 @@ public void testConvertFromMessageConnectWithPath() throws Exception { } @Test - public void testConvertFromMessageConnectionHeader() throws Exception { + void testConvertFromMessageConnectionHeader() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("Connection", "Keep-Alive"); @@ -356,7 +357,7 @@ public void testConvertFromMessageConnectionHeader() throws Exception { } @Test - public void testConvertFromFieldsKeepAliveHeader() throws Exception { + void testConvertFromFieldsKeepAliveHeader() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("Keep-Alive", "timeout=5, max=1000"); @@ -366,7 +367,7 @@ public void testConvertFromFieldsKeepAliveHeader() throws Exception { } @Test - public void testConvertFromFieldsProxyConnectionHeader() throws Exception { + void testConvertFromFieldsProxyConnectionHeader() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("Proxy-Connection", "keep-alive"); @@ -376,7 +377,7 @@ public void testConvertFromFieldsProxyConnectionHeader() throws Exception { } @Test - public void testConvertFromFieldsTransferEncodingHeader() throws Exception { + void testConvertFromFieldsTransferEncodingHeader() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("Transfer-Encoding", "gzip"); @@ -386,7 +387,7 @@ public void testConvertFromFieldsTransferEncodingHeader() throws Exception { } @Test - public void testConvertFromFieldsHostHeader() throws Exception { + void testConvertFromFieldsHostHeader() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("Host", "host"); @@ -396,7 +397,7 @@ public void testConvertFromFieldsHostHeader() throws Exception { } @Test - public void testConvertFromFieldsUpgradeHeader() throws Exception { + void testConvertFromFieldsUpgradeHeader() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("Upgrade", "example/1, foo/2"); @@ -406,7 +407,7 @@ public void testConvertFromFieldsUpgradeHeader() throws Exception { } @Test - public void testConvertFromFieldsTEHeader() throws Exception { + void testConvertFromFieldsTEHeader() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader("TE", "gzip"); @@ -416,7 +417,7 @@ public void testConvertFromFieldsTEHeader() throws Exception { } @Test - public void testConvertFromFieldsTETrailerHeader() throws Exception { + void testConvertFromFieldsTETrailerHeader() throws Exception { final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), @@ -439,7 +440,7 @@ public void testConvertFromFieldsTETrailerHeader() throws Exception { } @Test - public void testConvertFromMessageInvalidHeader() throws Exception { + void testConvertFromMessageInvalidHeader() { final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/"); request.addHeader(":custom", "stuff"); @@ -448,5 +449,99 @@ public void testConvertFromMessageInvalidHeader() throws Exception { "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)); + } + + } diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestDefaultH2ResponseConverter.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestDefaultH2ResponseConverter.java index 46d4e55025..2be82e2c5a 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestDefaultH2ResponseConverter.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestDefaultH2ResponseConverter.java @@ -38,10 +38,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestDefaultH2ResponseConverter { +class TestDefaultH2ResponseConverter { @Test - public void testConvertFromFieldsBasic() throws Exception { + void testConvertFromFieldsBasic() throws Exception { final List
headers = Arrays.asList( new BasicHeader(":status", "200"), @@ -61,7 +61,7 @@ public void testConvertFromFieldsBasic() throws Exception { } @Test - public void testConvertFromFieldsUpperCaseHeaderName() throws Exception { + void testConvertFromFieldsUpperCaseHeaderName() { final List
headers = Arrays.asList( new BasicHeader(":Status", "200"), new BasicHeader("location", "http://www.example.com/"), @@ -73,7 +73,7 @@ public void testConvertFromFieldsUpperCaseHeaderName() throws Exception { } @Test - public void testConvertFromFieldsInvalidStatusCode() throws Exception { + void testConvertFromFieldsInvalidStatusCode() { final List
headers = Arrays.asList( new BasicHeader(":status", "boom"), new BasicHeader("location", "http://www.example.com/"), @@ -84,7 +84,7 @@ public void testConvertFromFieldsInvalidStatusCode() throws Exception { } @Test - public void testConvertFromFieldsConnectionHeader() throws Exception { + void testConvertFromFieldsConnectionHeader() { final List
headers = Arrays.asList( new BasicHeader(":status", "200"), new BasicHeader("location", "http://www.example.com/"), @@ -96,7 +96,7 @@ public void testConvertFromFieldsConnectionHeader() throws Exception { } @Test - public void testConvertFromFieldsKeepAliveHeader() throws Exception { + void testConvertFromFieldsKeepAliveHeader() { final List
headers = Arrays.asList( new BasicHeader(":status", "200"), new BasicHeader("location", "http://www.example.com/"), @@ -108,7 +108,7 @@ public void testConvertFromFieldsKeepAliveHeader() throws Exception { } @Test - public void testConvertFromFieldsTransferEncodingHeader() throws Exception { + void testConvertFromFieldsTransferEncodingHeader() { final List
headers = Arrays.asList( new BasicHeader(":status", "200"), new BasicHeader("location", "http://www.example.com/"), @@ -120,7 +120,7 @@ public void testConvertFromFieldsTransferEncodingHeader() throws Exception { } @Test - public void testConvertFromFieldsUpgradeHeader() throws Exception { + void testConvertFromFieldsUpgradeHeader() { final List
headers = Arrays.asList( new BasicHeader(":status", "200"), new BasicHeader("location", "http://www.example.com/"), @@ -132,7 +132,7 @@ public void testConvertFromFieldsUpgradeHeader() throws Exception { } @Test - public void testConvertFromFieldsMissingStatus() throws Exception { + void testConvertFromFieldsMissingStatus() { final List
headers = Arrays.asList( new BasicHeader("location", "http://www.example.com/"), new BasicHeader("custom", "value")); @@ -143,7 +143,7 @@ public void testConvertFromFieldsMissingStatus() throws Exception { } @Test - public void testConvertFromFieldsUnknownPseudoHeader() throws Exception { + void testConvertFromFieldsUnknownPseudoHeader() { final List
headers = Arrays.asList( new BasicHeader(":status", "200"), new BasicHeader(":custom", "200"), @@ -156,7 +156,7 @@ public void testConvertFromFieldsUnknownPseudoHeader() throws Exception { } @Test - public void testConvertFromFieldsMultipleStatus() throws Exception { + void testConvertFromFieldsMultipleStatus() { final List
headers = Arrays.asList( new BasicHeader(":status", "200"), new BasicHeader(":status", "200"), @@ -169,7 +169,7 @@ public void testConvertFromFieldsMultipleStatus() throws Exception { } @Test - public void testConvertFromMessageBasic() throws Exception { + void testConvertFromMessageBasic() throws Exception { final HttpResponse response = new BasicHttpResponse(200); response.addHeader("custom123", "Value"); @@ -188,7 +188,7 @@ public void testConvertFromMessageBasic() throws Exception { } @Test - public void testConvertFromMessageInvalidStatus() throws Exception { + void testConvertFromMessageInvalidStatus() { final HttpResponse response = new BasicHttpResponse(99); response.addHeader("Custom123", "Value"); @@ -198,7 +198,7 @@ public void testConvertFromMessageInvalidStatus() throws Exception { } @Test - public void testConvertFromMessageConnectionHeader() throws Exception { + void testConvertFromMessageConnectionHeader() { final HttpResponse response = new BasicHttpResponse(200); response.addHeader("Connection", "Keep-Alive"); @@ -208,7 +208,7 @@ public void testConvertFromMessageConnectionHeader() throws Exception { } @Test - public void testConvertFromMessageInvalidHeader() throws Exception { + void testConvertFromMessageInvalidHeader() { final HttpResponse response = new BasicHttpResponse(200); response.addHeader(":custom", "stuff"); diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/io/MultiByteArrayInputStream.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/io/MultiByteArrayInputStream.java index 8f72d56aab..d4d0768f94 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/io/MultiByteArrayInputStream.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/io/MultiByteArrayInputStream.java @@ -77,6 +77,9 @@ public int read(final byte b[], final int off, final int len) throws IOException ((off + len) > b.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } + if (len == 0) { + return 0; + } advance(); if (this.current == null) { return -1; diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/io/TestFrameInOutBuffers.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/io/TestFrameInOutBuffers.java index 1800e423f3..617f599828 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/io/TestFrameInOutBuffers.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/io/TestFrameInOutBuffers.java @@ -42,10 +42,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestFrameInOutBuffers { +class TestFrameInOutBuffers { @Test - public void testReadWriteFrame() throws Exception { + void testReadWriteFrame() throws Exception { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final FrameOutputBuffer outbuffer = new FrameOutputBuffer(16 * 1024); @@ -80,7 +80,7 @@ public void testReadWriteFrame() throws Exception { } @Test - public void testReadFrameMultiple() throws Exception { + void testReadFrameMultiple() throws Exception { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final ByteArrayInputStream inputStream = new ByteArrayInputStream( new byte[] { @@ -118,7 +118,7 @@ public void testReadFrameMultiple() throws Exception { } @Test - public void testReadFrameMultipleSmallBuffer() throws Exception { + void testReadFrameMultipleSmallBuffer() throws Exception { final FrameInputBuffer inBuffer = new FrameInputBuffer(new BasicH2TransportMetrics(), 20, 10); final ByteArrayInputStream inputStream = new ByteArrayInputStream( new byte[] { @@ -170,7 +170,7 @@ public void testReadFrameMultipleSmallBuffer() throws Exception { } @Test - public void testReadFramePartialReads() throws Exception { + void testReadFramePartialReads() throws Exception { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final MultiByteArrayInputStream inputStream = new MultiByteArrayInputStream( new byte[] {0,0}, @@ -197,7 +197,7 @@ public void testReadFramePartialReads() throws Exception { } @Test - public void testReadEmptyFrame() throws Exception { + 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}); @@ -210,7 +210,7 @@ public void testReadEmptyFrame() throws Exception { } @Test - public void testReadFrameConnectionClosed() throws Exception { + void testReadFrameConnectionClosed() { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[] {}); @@ -218,7 +218,7 @@ public void testReadFrameConnectionClosed() throws Exception { } @Test - public void testReadFrameCorruptFrame() throws Exception { + void testReadFrameCorruptFrame() { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[] {0,0}); @@ -226,7 +226,7 @@ public void testReadFrameCorruptFrame() throws Exception { } @Test - public void testWriteFrameExceedingLimit() throws Exception { + void testWriteFrameExceedingLimit() { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final FrameOutputBuffer outbuffer = new FrameOutputBuffer(1024); @@ -236,7 +236,7 @@ public void testWriteFrameExceedingLimit() throws Exception { } @Test - public void testReadFrameExceedingLimit() throws Exception { + 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}); diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestAbstractH2StreamMultiplexer.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestAbstractH2StreamMultiplexer.java index 2a6fc53e5e..976d1913bb 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestAbstractH2StreamMultiplexer.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestAbstractH2StreamMultiplexer.java @@ -49,11 +49,12 @@ 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; -public class TestAbstractH2StreamMultiplexer { +class TestAbstractH2StreamMultiplexer { @Mock ProtocolIOSession protocolIOSession; @@ -63,7 +64,7 @@ public class TestAbstractH2StreamMultiplexer { H2StreamListener h2StreamListener; @BeforeEach - public void prepareMocks() { + void prepareMocks() { MockitoAnnotations.openMocks(this); } @@ -112,7 +113,7 @@ H2StreamHandler createLocallyInitiatedStream( } @Test - public void testInputOneFrame() throws Exception { + void testInputOneFrame() throws Exception { final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); final FrameOutputBuffer outbuffer = new FrameOutputBuffer(16 * 1024); @@ -139,9 +140,9 @@ public void testInputOneFrame() throws Exception { Assertions.assertThrows(H2ConnectionException.class, () -> streamMultiplexer.onInput(ByteBuffer.wrap(bytes))); Mockito.verify(h2StreamListener).onFrameInput( - Mockito.same(streamMultiplexer), - Mockito.eq(1), - Mockito.any()); + ArgumentMatchers.same(streamMultiplexer), + ArgumentMatchers.eq(1), + ArgumentMatchers.any()); Assertions.assertThrows(H2ConnectionException.class, () -> { int pos = 0; @@ -154,14 +155,14 @@ public void testInputOneFrame() throws Exception { } Mockito.verify(h2StreamListener).onFrameInput( - Mockito.same(streamMultiplexer), - Mockito.eq(1), - Mockito.any()); + ArgumentMatchers.same(streamMultiplexer), + ArgumentMatchers.eq(1), + ArgumentMatchers.any()); }); } @Test - public void testInputMultipleFrames() throws Exception { + void testInputMultipleFrames() throws Exception { final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); final FrameOutputBuffer outbuffer = new FrameOutputBuffer(16 * 1024); @@ -190,9 +191,9 @@ public void testInputMultipleFrames() throws Exception { Assertions.assertThrows(H2ConnectionException.class, () -> streamMultiplexer.onInput(ByteBuffer.wrap(bytes))); Mockito.verify(h2StreamListener).onFrameInput( - Mockito.same(streamMultiplexer), - Mockito.eq(1), - Mockito.any()); + ArgumentMatchers.same(streamMultiplexer), + ArgumentMatchers.eq(1), + ArgumentMatchers.any()); Assertions.assertThrows(H2ConnectionException.class, () -> { int pos = 0; @@ -205,9 +206,9 @@ public void testInputMultipleFrames() throws Exception { } Mockito.verify(h2StreamListener).onFrameInput( - Mockito.same(streamMultiplexer), - Mockito.eq(1), - Mockito.any()); + ArgumentMatchers.same(streamMultiplexer), + ArgumentMatchers.eq(1), + ArgumentMatchers.any()); }); } diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestFrameInOutBuffers.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestFrameInOutBuffers.java index e949535af9..ce889f2e86 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestFrameInOutBuffers.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestFrameInOutBuffers.java @@ -42,10 +42,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestFrameInOutBuffers { +class TestFrameInOutBuffers { @Test - public void testReadWriteFrame() throws Exception { + void testReadWriteFrame() throws Exception { final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); final FrameOutputBuffer outbuffer = new FrameOutputBuffer(16 * 1024); @@ -96,7 +96,7 @@ public void testReadWriteFrame() throws Exception { } @Test - public void testPartialFrameWrite() throws Exception { + 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, @@ -118,7 +118,7 @@ public void testPartialFrameWrite() throws Exception { } @Test - public void testReadFrameMultiple() throws Exception { + void testReadFrameMultiple() throws Exception { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final ReadableByteChannelMock readableChannel = new ReadableByteChannelMock( new byte[] { @@ -156,7 +156,7 @@ public void testReadFrameMultiple() throws Exception { } @Test - public void testReadFrameMultipleSmallBuffer() throws Exception { + void testReadFrameMultipleSmallBuffer() throws Exception { final FrameInputBuffer inBuffer = new FrameInputBuffer(new BasicH2TransportMetrics(), 20, 10); final ReadableByteChannelMock readableChannel = new ReadableByteChannelMock( new byte[] { @@ -208,7 +208,7 @@ public void testReadFrameMultipleSmallBuffer() throws Exception { } @Test - public void testReadFramePartialReads() throws Exception { + void testReadFramePartialReads() throws Exception { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final ReadableByteChannelMock readableChannel = new ReadableByteChannelMock( new byte[] {0,0}, @@ -236,7 +236,7 @@ public void testReadFramePartialReads() throws Exception { } @Test - public void testReadEmptyFrame() throws Exception { + 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}); @@ -250,7 +250,7 @@ public void testReadEmptyFrame() throws Exception { } @Test - public void testReadFrameConnectionClosed() throws Exception { + void testReadFrameConnectionClosed() throws Exception { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final ReadableByteChannelMock readableChannel = new ReadableByteChannelMock(new byte[] {}); @@ -260,7 +260,7 @@ public void testReadFrameConnectionClosed() throws Exception { } @Test - public void testReadFrameCorruptFrame() throws Exception { + void testReadFrameCorruptFrame() { final FrameInputBuffer inBuffer = new FrameInputBuffer(16 * 1024); final ReadableByteChannelMock readableChannel = new ReadableByteChannelMock(new byte[] {0,0}); @@ -269,7 +269,7 @@ public void testReadFrameCorruptFrame() throws Exception { } @Test - public void testWriteFrameExceedingLimit() throws Exception { + void testWriteFrameExceedingLimit() { final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); final FrameOutputBuffer outbuffer = new FrameOutputBuffer(1024); @@ -280,7 +280,7 @@ public void testWriteFrameExceedingLimit() throws Exception { } @Test - public void testReadFrameExceedingLimit() throws Exception { + 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}); @@ -290,7 +290,7 @@ public void testReadFrameExceedingLimit() throws Exception { } @Test - public void testOutputBufferResize() throws Exception { + void testOutputBufferResize() { final FrameOutputBuffer outBuffer = new FrameOutputBuffer(16 * 1024); Assertions.assertEquals(16 * 1024, outBuffer.getMaxFramePayloadSize()); outBuffer.resize(1024); diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/protocol/TestH2Interceptors.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/protocol/TestH2Interceptors.java new file mode 100644 index 0000000000..17ac94817e --- /dev/null +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/protocol/TestH2Interceptors.java @@ -0,0 +1,144 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by 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.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.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.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); + } + +} diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/ssl/ConscryptClientTlsStrategyTest.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/ssl/ConscryptClientTlsStrategyTest.java index 4526ce0e3c..ed3c0fc6da 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/ssl/ConscryptClientTlsStrategyTest.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/ssl/ConscryptClientTlsStrategyTest.java @@ -41,7 +41,7 @@ class ConscryptClientTlsStrategyTest { private SSLSessionVerifier sslSessionVerifier; @BeforeEach - public void prepareMocks() { + void prepareMocks() { MockitoAnnotations.openMocks(this); } diff --git a/httpcore5-reactive/pom.xml b/httpcore5-reactive/pom.xml index baad733a57..9caf619c44 100644 --- a/httpcore5-reactive/pom.xml +++ b/httpcore5-reactive/pom.xml @@ -27,7 +27,7 @@ httpcore5-parent org.apache.httpcomponents.core5 - 5.2.4-SNAPSHOT + 5.3 4.0.0 diff --git a/httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveDataConsumer.java b/httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveDataConsumer.java index 85604e0484..0414326043 100644 --- a/httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveDataConsumer.java +++ b/httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveDataConsumer.java @@ -34,6 +34,7 @@ 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; @@ -57,8 +58,7 @@ final class ReactiveDataConsumer implements AsyncDataConsumer, Publisher buffers = new LinkedBlockingQueue<>(); - private final AtomicBoolean flushInProgress = new AtomicBoolean(false); - private final Object flushLock = new Object(); + private final AtomicBoolean flushInProgress = new AtomicBoolean(); private final AtomicInteger windowScalingIncrement = new AtomicInteger(0); private volatile boolean cancelled; private volatile boolean completed; @@ -66,6 +66,8 @@ final class ReactiveDataConsumer implements AsyncDataConsumer, Publisher subscriber; + private final ReentrantLock lock = new ReentrantLock(); + public void failed(final Exception cause) { if (!completed) { exception = cause; @@ -119,7 +121,8 @@ public void releaseResources() { } private void flushToSubscriber() { - synchronized (flushLock) { + lock.lock(); + try { final Subscriber s = subscriber; if (flushInProgress.getAndSet(true)) { return; @@ -157,6 +160,8 @@ private void flushToSubscriber() { } finally { flushInProgress.set(false); } + } finally { + lock.unlock(); } } diff --git a/httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveDataProducer.java b/httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveDataProducer.java index 61813caeba..55981b85cd 100644 --- a/httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveDataProducer.java +++ b/httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveDataProducer.java @@ -31,6 +31,7 @@ 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; @@ -54,13 +55,16 @@ final class ReactiveDataProducer implements AsyncDataProducer, Subscriber requestChannel = new AtomicReference<>(); private final AtomicReference exception = new AtomicReference<>(); - private final AtomicBoolean complete = new AtomicBoolean(false); + 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) { @@ -80,8 +84,11 @@ public void onSubscribe(final Subscription subscription) { public void onNext(final ByteBuffer byteBuffer) { final byte[] copy = new byte[byteBuffer.remaining()]; byteBuffer.get(copy); - synchronized (buffers) { + lock.lock(); + try { buffers.add(ByteBuffer.wrap(copy)); + } finally { + lock.unlock(); } signalReadiness(); } @@ -113,12 +120,15 @@ public int available() { if (exception.get() != null || complete.get()) { return 1; } else { - synchronized (buffers) { + lock.lock(); + try { int sum = 0; for (final ByteBuffer buffer : buffers) { sum += buffer.remaining(); } return sum; + } finally { + lock.unlock(); } } } @@ -134,7 +144,8 @@ public void produce(final DataStreamChannel channel) throws IOException { final Subscription s = subscription.get(); int buffersToReplenish = 0; try { - synchronized (buffers) { + lock.lock(); + try { if (t != null) { throw new HttpStreamResetException(t.getMessage(), t); } else if (this.complete.get() && buffers.isEmpty()) { @@ -152,6 +163,8 @@ public void produce(final DataStreamChannel channel) throws IOException { } } } + } finally { + lock.unlock(); } } finally { if (s != null && buffersToReplenish > 0) { diff --git a/httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveEntityProducer.java b/httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveEntityProducer.java index dcf521af0a..8d03441549 100644 --- a/httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveEntityProducer.java +++ b/httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveEntityProducer.java @@ -35,6 +35,8 @@ import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.Objects; import java.util.Set; /** @@ -104,7 +106,7 @@ public long getContentLength() { @Override public String getContentType() { - return contentType != null ? contentType.toString() : null; + return Objects.toString(contentType, null); } @Override @@ -119,6 +121,6 @@ public boolean isChunked() { @Override public Set getTrailerNames() { - return null; + return Collections.emptySet(); } } diff --git a/httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveRequestProcessor.java b/httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveRequestProcessor.java index e639be9200..a403114aad 100644 --- a/httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveRequestProcessor.java +++ b/httpcore5-reactive/src/main/java/org/apache/hc/core5/reactive/ReactiveRequestProcessor.java @@ -54,6 +54,8 @@ public interface ReactiveRequestProcessor { * @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, diff --git a/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/BasicDataStreamChannel.java b/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/BasicDataStreamChannel.java index 6510f040b0..6e5fd76b30 100644 --- a/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/BasicDataStreamChannel.java +++ b/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/BasicDataStreamChannel.java @@ -36,6 +36,9 @@ import java.util.ArrayList; import java.util.List; +/** + * Writes to a byte stream channel. + */ public class BasicDataStreamChannel implements DataStreamChannel { private final WritableByteChannel byteChannel; diff --git a/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveDataConsumer.java b/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveDataConsumer.java index 40d8a7a798..f7b5b4b381 100644 --- a/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveDataConsumer.java +++ b/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveDataConsumer.java @@ -46,10 +46,10 @@ import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; -public class TestReactiveDataConsumer { +class TestReactiveDataConsumer { @Test - public void testStreamThatEndsNormally() throws Exception { + void testStreamThatEndsNormally() throws Exception { final ReactiveDataConsumer consumer = new ReactiveDataConsumer(); final List output = Collections.synchronizedList(new ArrayList<>()); @@ -80,7 +80,7 @@ public void testStreamThatEndsNormally() throws Exception { } @Test - public void testStreamThatEndsWithError() { + void testStreamThatEndsWithError() { final ReactiveDataConsumer consumer = new ReactiveDataConsumer(); final Single>> single = Observable.fromPublisher(consumer) .materialize() @@ -93,7 +93,7 @@ public void testStreamThatEndsWithError() { } @Test - public void testCancellation() throws Exception { + void testCancellation() { final ReactiveDataConsumer consumer = new ReactiveDataConsumer(); consumer.subscribe(new Subscriber() { @Override @@ -119,7 +119,7 @@ public void onComplete() { } @Test - public void testCapacityIncrements() throws Exception { + void testCapacityIncrements() throws Exception { final ReactiveDataConsumer consumer = new ReactiveDataConsumer(); final ByteBuffer data = ByteBuffer.wrap(new byte[1024]); @@ -166,7 +166,7 @@ public void onComplete() { } @Test - public void testFullResponseBuffering() throws Exception { + 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(); @@ -177,11 +177,11 @@ public void testFullResponseBuffering() throws Exception { consumer.consume(data.duplicate()); consumer.streamEnd(null); - Assertions.assertEquals(Flowable.fromPublisher(consumer).count().blockingGet().longValue(), 3L); + Assertions.assertEquals(3L, Flowable.fromPublisher(consumer).count().blockingGet().longValue()); } @Test - public void testErrorBuffering() throws Exception { + void testErrorBuffering() throws Exception { final ReactiveDataConsumer consumer = new ReactiveDataConsumer(); final ByteBuffer data = ByteBuffer.wrap(new byte[1024]); @@ -199,7 +199,7 @@ public void testErrorBuffering() throws Exception { } @Test - public void testFailAfterCompletion() { + 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. diff --git a/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveDataProducer.java b/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveDataProducer.java index 96a674776a..0f5e04f39e 100644 --- a/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveDataProducer.java +++ b/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveDataProducer.java @@ -36,9 +36,9 @@ import io.reactivex.rxjava3.core.Flowable; -public class TestReactiveDataProducer { +class TestReactiveDataProducer { @Test - public void testStreamThatEndsNormally() throws Exception { + void testStreamThatEndsNormally() throws Exception { final Flowable publisher = Flowable.just( ByteBuffer.wrap(new byte[]{ '1', '2', '3' }), ByteBuffer.wrap(new byte[]{ '4', '5', '6' })); @@ -59,7 +59,7 @@ public void testStreamThatEndsNormally() throws Exception { } @Test - public void testStreamThatEndsWithError() throws Exception { + void testStreamThatEndsWithError() throws Exception { final Flowable publisher = Flowable.concatArray( Flowable.just( ByteBuffer.wrap(new byte[]{ '1' }), diff --git a/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveEntityProducer.java b/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveEntityProducer.java index e89402b567..fbcf367fdd 100644 --- a/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveEntityProducer.java +++ b/httpcore5-reactive/src/test/java/org/apache/hc/core5/reactive/TestReactiveEntityProducer.java @@ -28,6 +28,7 @@ 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; @@ -37,14 +38,14 @@ import io.reactivex.rxjava3.core.Flowable; -public class TestReactiveEntityProducer { +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 - public void testStreamThatEndsNormally() throws Exception { + void testStreamThatEndsNormally() throws Exception { final Flowable publisher = Flowable.just( ByteBuffer.wrap(new byte[]{'1', '2', '3'}), ByteBuffer.wrap(new byte[]{'4', '5', '6'})); @@ -64,7 +65,7 @@ public void testStreamThatEndsNormally() throws Exception { Assertions.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII)); Assertions.assertFalse(entityProducer.isChunked()); Assertions.assertEquals(GZIP_CONTENT_ENCODING, entityProducer.getContentEncoding()); - Assertions.assertNull(entityProducer.getTrailerNames()); + Assertions.assertEquals(Collections.emptySet(), entityProducer.getTrailerNames()); Assertions.assertEquals(CONTENT_LENGTH, entityProducer.getContentLength()); Assertions.assertEquals(CONTENT_TYPE.toString(), entityProducer.getContentType()); Assertions.assertFalse(entityProducer.isRepeatable()); @@ -75,7 +76,7 @@ public void testStreamThatEndsNormally() throws Exception { @Test - public void testStreamThatEndsWithError() throws Exception { + void testStreamThatEndsWithError() throws Exception { final Flowable publisher = Flowable.concatArray( Flowable.just( ByteBuffer.wrap(new byte[]{'1'}), @@ -105,7 +106,7 @@ public void testStreamThatEndsWithError() throws Exception { Assertions.assertEquals("", byteChannel.dump(StandardCharsets.US_ASCII)); Assertions.assertFalse(entityProducer.isChunked()); Assertions.assertEquals(GZIP_CONTENT_ENCODING, entityProducer.getContentEncoding()); - Assertions.assertNull(entityProducer.getTrailerNames()); + Assertions.assertEquals(Collections.emptySet(), entityProducer.getTrailerNames()); Assertions.assertEquals(CONTENT_LENGTH, entityProducer.getContentLength()); Assertions.assertEquals(CONTENT_TYPE.toString(), entityProducer.getContentType()); Assertions.assertFalse(entityProducer.isRepeatable()); diff --git a/httpcore5-testing/pom.xml b/httpcore5-testing/pom.xml index 3b2c798e42..ac690b3bd3 100644 --- a/httpcore5-testing/pom.xml +++ b/httpcore5-testing/pom.xml @@ -28,7 +28,7 @@ org.apache.httpcomponents.core5 httpcore5-parent - 5.2.4-SNAPSHOT + 5.3 httpcore5-testing Apache HttpComponents Core Integration Tests @@ -54,7 +54,7 @@ commons-cli commons-cli - 1.5.0 + 1.9.0 org.slf4j @@ -70,6 +70,11 @@ rxjava ${rxjava3.version} + + org.apache.httpcomponents.core5 + httpcore5 + tests + org.apache.logging.log4j log4j-slf4j-impl @@ -125,7 +130,7 @@ io.fabric8 docker-maven-plugin - 0.31.0 + 0.45.0 start-httpbin diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/SocksProxy.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/SocksProxy.java index 7d8234d8e6..f4a5609eb1 100644 --- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/SocksProxy.java +++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/SocksProxy.java @@ -37,6 +37,7 @@ 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; @@ -209,6 +210,7 @@ public void shutdown() { private final List handlers = new ArrayList<>(); private ServerSocket server; private Thread serverThread; + private final ReentrantLock lock; public SocksProxy() { this(0); @@ -216,36 +218,43 @@ public SocksProxy() { public SocksProxy(final int port) { this.port = port; + this.lock = new ReentrantLock(); } - public synchronized void start() throws IOException { - 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 e) { - } finally { - if (server != null) { - try { - server.close(); - } catch (final IOException e) { + 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 e) { + } finally { + if (server != null) { + try { + server.close(); + } catch (final IOException e) { + } + server = null; } - server = null; } - } - }); - this.serverThread.start(); + }); + this.serverThread.start(); + } + } finally { + lock.unlock(); } } public void shutdown(final TimeValue timeout) throws InterruptedException { final long waitUntil = System.currentTimeMillis() + timeout.toMilliseconds(); Thread t = null; - synchronized (this) { + lock.lock(); + try { if (this.server != null) { try { this.server.close(); @@ -265,6 +274,8 @@ public void shutdown(final TimeValue timeout) throws InterruptedException { wait(waitTime); } } + } finally { + lock.unlock(); } if (t != null) { final long waitTime = waitUntil - System.currentTimeMillis(); @@ -276,14 +287,22 @@ public void shutdown(final TimeValue timeout) throws InterruptedException { protected void startSocksProxyHandler(final Socket socket) { final SocksProxyHandler handler = new SocksProxyHandler(this, socket); - synchronized (this) { + lock.lock(); + try { this.handlers.add(handler); + } finally { + lock.unlock(); } handler.start(); } - protected synchronized void cleanupSocksProxyHandler(final SocksProxyHandler handler) { - this.handlers.remove(handler); + protected void cleanupSocksProxyHandler(final SocksProxyHandler handler) { + lock.lock(); + try { + this.handlers.remove(handler); + } finally { + lock.unlock(); + } } public SocketAddress getProxyAddress() { diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/ClassicTestClient.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/ClassicTestClient.java index efb1730cd5..62d053b7b1 100644 --- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/ClassicTestClient.java +++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/ClassicTestClient.java @@ -51,6 +51,7 @@ 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 { @@ -60,6 +61,8 @@ public class ClassicTestClient { private final AtomicReference requesterRef; + private HttpProcessor httpProcessor; + public ClassicTestClient(final SSLContext sslContext, final SocketConfig socketConfig) { super(); this.sslContext = sslContext; @@ -75,14 +78,38 @@ public ClassicTestClient() { this(null, null); } - public void start() { - start(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( - HttpRequestExecutor.DEFAULT_WAIT_FOR_CONTINUE, + Http1Config.DEFAULT, DefaultConnectionReuseStrategy.INSTANCE, LoggingHttp1StreamListener.INSTANCE); final StrictConnPool connPool = new StrictConnPool<>( @@ -118,10 +145,7 @@ public ClassicHttpResponse execute( final HttpHost targetHost, final ClassicHttpRequest request, final HttpContext context) throws HttpException, IOException { - final HttpRequester requester = this.requesterRef.get(); - if (requester == null) { - throw new IllegalStateException("Requester has not been started"); - } + final HttpRequester requester = ensureRunning(); if (request.getAuthority() == null) { request.setAuthority(new URIAuthority(targetHost)); } diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/ClassicTestServer.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/ClassicTestServer.java index 2e94c77540..01f624292c 100644 --- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/ClassicTestServer.java +++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/ClassicTestServer.java @@ -29,9 +29,10 @@ 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.ServerSocketFactory; import javax.net.ssl.SSLContext; import org.apache.hc.core5.function.Decorator; @@ -42,28 +43,35 @@ 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.RequestHandlerRegistry; +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 RequestHandlerRegistry registry; + 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.registry = new RequestHandlerRegistry<>(); + this.routeEntries = new ArrayList<>(); this.serverRef = new AtomicReference<>(); } @@ -75,36 +83,106 @@ public ClassicTestServer() { this(null, null); } + /** + * @deprecated Use {@link #register(String, HttpRequestHandler)}. + */ + @Deprecated public void registerHandler(final String pattern, final HttpRequestHandler handler) { - this.registry.register(null, pattern, handler); + register(pattern, handler); } + /** + * @deprecated Use {@link #register(String, String, HttpRequestHandler)}. + */ + @Deprecated public void registerHandlerVirtual(final String hostname, final String pattern, final HttpRequestHandler handler) { - this.registry.register(hostname, pattern, handler); + register(hostname, pattern, handler); } - public int getPort() { + private HttpServer ensureRunning() { final HttpServer server = this.serverRef.get(); - if (server != null) { - return server.getLocalPort(); - } - throw new IllegalStateException("Server not running"); + Asserts.check(server != null, "Server is not running"); + return server; } - public InetAddress getInetAddress() { + private void ensureNotRunning() { final HttpServer server = this.serverRef.get(); - if (server != null) { - return server.getInetAddress(); - } - throw new IllegalStateException("Server not running"); + 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(registry); + 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), @@ -115,11 +193,12 @@ public void start( httpService, null, socketConfig, - sslContext != null ? sslContext.getServerSocketFactory() : ServerSocketFactory.getDefault(), + 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)) { @@ -130,10 +209,6 @@ public void start( } } - public void start() throws IOException { - start(null, null, null); - } - public void shutdown(final CloseMode closeMode) { final HttpServer server = serverRef.getAndSet(null); if (server != null) { diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpClientConnection.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpClientConnection.java index 7fcdd4afc1..87fbed832e 100644 --- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpClientConnection.java +++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpClientConnection.java @@ -33,6 +33,8 @@ 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; @@ -46,8 +48,8 @@ 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.LoggerFactory; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class LoggingBHttpClientConnection extends DefaultBHttpClientConnection implements Identifiable { @@ -105,6 +107,13 @@ 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()) { diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpClientConnectionFactory.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpClientConnectionFactory.java index 40ed703faa..cb1a5d172a 100644 --- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpClientConnectionFactory.java +++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpClientConnectionFactory.java @@ -30,6 +30,8 @@ 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; @@ -85,9 +87,8 @@ public LoggingBHttpClientConnectionFactory() { this(null, null, null, null, null, null); } - @Override - public LoggingBHttpClientConnection createConnection(final Socket socket) throws IOException { - final LoggingBHttpClientConnection conn = new LoggingBHttpClientConnection( + LoggingBHttpClientConnection createDetached() { + return new LoggingBHttpClientConnection( http1Config, CharCodingSupport.createDecoder(charCodingConfig), CharCodingSupport.createEncoder(charCodingConfig), @@ -95,8 +96,23 @@ public LoggingBHttpClientConnection createConnection(final Socket socket) throws 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; + } + } diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpServerConnection.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpServerConnection.java index 94ed2f38e8..b275a05595 100644 --- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpServerConnection.java +++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpServerConnection.java @@ -33,6 +33,8 @@ 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; @@ -46,8 +48,8 @@ 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.LoggerFactory; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class LoggingBHttpServerConnection extends DefaultBHttpServerConnection implements Identifiable { private static final AtomicLong COUNT = new AtomicLong(); @@ -105,6 +107,13 @@ 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()) { diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpServerConnectionFactory.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpServerConnectionFactory.java index 8e9a2aa7d2..0a97e48618 100644 --- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpServerConnectionFactory.java +++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingBHttpServerConnectionFactory.java @@ -93,9 +93,8 @@ public LoggingBHttpServerConnectionFactory() { this(null, null, null, null, null, null, null); } - @Override - public LoggingBHttpServerConnection createConnection(final Socket socket) throws IOException { - final LoggingBHttpServerConnection conn = new LoggingBHttpServerConnection( + LoggingBHttpServerConnection createDetached(final Socket socket) { + return new LoggingBHttpServerConnection( scheme != null ? scheme : (socket instanceof SSLSocket ? URIScheme.HTTPS.id : URIScheme.HTTP.id), http1Config, CharCodingSupport.createDecoder(charCodingConfig), @@ -104,8 +103,23 @@ public LoggingBHttpServerConnection createConnection(final Socket socket) throws 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; + } + } diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingSocketHolder.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingSocketHolder.java index 06dbd78a03..350a656255 100644 --- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingSocketHolder.java +++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/LoggingSocketHolder.java @@ -32,12 +32,19 @@ 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; diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/ClassicTestClientAdapter.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/ClassicTestClientAdapter.java index 1c4b097c6e..9c83e80108 100644 --- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/ClassicTestClientAdapter.java +++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/framework/ClassicTestClientAdapter.java @@ -135,7 +135,7 @@ public Map execute(final String defaultURI, final Map()) .setSocketConfig(socketConfig) - .register("/*", requestHandler); + .setRequestRouter(RequestRouter.builder() + .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", requestHandler) + .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) + .build()); server = serverBootstrap.create(); try { diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/ClientSessionEndpoint.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/ClientSessionEndpoint.java index 824708d767..ba3761b66e 100644 --- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/ClientSessionEndpoint.java +++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/ClientSessionEndpoint.java @@ -66,7 +66,7 @@ public final class ClientSessionEndpoint implements ModalCloseable { public ClientSessionEndpoint(final IOSession ioSession) { super(); this.ioSession = ioSession; - this.closed = new AtomicBoolean(false); + this.closed = new AtomicBoolean(); } public void execute(final Command command, final Command.Priority priority) { diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/H2TestClient.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/H2TestClient.java index 7197ef6312..a8839f8380 100644 --- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/H2TestClient.java +++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/H2TestClient.java @@ -28,30 +28,36 @@ package org.apache.hc.core5.testing.nio; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Future; import javax.net.ssl.SSLContext; -import org.apache.hc.core5.concurrent.FutureContribution; import org.apache.hc.core5.concurrent.BasicFuture; 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.HttpHost; 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.RequestHandlerRegistry; +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.IOReactorStatus; import org.apache.hc.core5.reactor.IOSession; 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; import org.apache.hc.core5.util.Timeout; public class H2TestClient extends AsyncRequester { @@ -59,7 +65,11 @@ public class H2TestClient extends AsyncRequester { private final SSLContext sslContext; private final SSLSessionInitializer sslSessionInitializer; private final SSLSessionVerifier sslSessionVerifier; - private final RequestHandlerRegistry> registry; + private final List>> routeEntries; + + private H2Config h2Config; + private Http1Config http1Config; + private HttpProcessor httpProcessor; public H2TestClient( final IOReactorConfig ioReactorConfig, @@ -70,59 +80,128 @@ public H2TestClient( this.sslContext = sslContext; this.sslSessionInitializer = sslSessionInitializer; this.sslSessionVerifier = sslSessionVerifier; - this.registry = new RequestHandlerRegistry<>(); + this.routeEntries = new ArrayList<>(); } public H2TestClient() throws IOException { this(IOReactorConfig.DEFAULT, null, null, null); } + private void ensureNotRunning() { + Asserts.check(getStatus() == IOReactorStatus.INACTIVE, "Client is already running"); + } + public void register(final String uriPattern, final Supplier supplier) { Args.notNull(uriPattern, "URI pattern"); - Args.notNull(supplier, "Supplier"); - registry.register(null, uriPattern, supplier); + 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; + } + + /** + * @since 5.3 + */ + public void configure(final HttpProcessor httpProcessor) { + ensureNotRunning(); + this.httpProcessor = httpProcessor; } 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 { - start(new InternalClientProtocolNegotiationStarter( - httpProcessor, - new DefaultAsyncPushConsumerFactory(registry), - HttpVersionPolicy.FORCE_HTTP_2, - h2Config, - Http1Config.DEFAULT, - CharCodingConfig.DEFAULT, - sslContext, - sslSessionInitializer, - sslSessionVerifier)); + 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 { - start(new InternalClientProtocolNegotiationStarter( - httpProcessor, - new DefaultAsyncPushConsumerFactory(registry), - HttpVersionPolicy.FORCE_HTTP_1, - H2Config.DEFAULT, - http1Config, - CharCodingConfig.DEFAULT, - sslContext, - sslSessionInitializer, - sslSessionVerifier)); + 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(H2Processors.client(), h2Config); + start(null, h2Config); } + /** + * @deprecated Use {@link #configure(Http1Config)}, {@link #start()}. + */ + @Deprecated public void start(final Http1Config http1Config) throws IOException { - start(H2Processors.client(), http1Config); + start(null, http1Config); } public void start() throws Exception { - start(H2Config.DEFAULT); + 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)); + } 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)); + } + } public Future connect( diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/H2TestServer.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/H2TestServer.java index 149af46f6a..237ef5b048 100644 --- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/H2TestServer.java +++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/H2TestServer.java @@ -29,6 +29,8 @@ import java.io.IOException; import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Future; import javax.net.ssl.SSLContext; @@ -38,28 +40,37 @@ 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.AsyncServerRequestHandler; 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.protocol.HttpProcessor; -import org.apache.hc.core5.http.protocol.RequestHandlerRegistry; +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.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; public class H2TestServer extends AsyncServer { private final SSLContext sslContext; private final SSLSessionInitializer sslSessionInitializer; private final SSLSessionVerifier sslSessionVerifier; - private final RequestHandlerRegistry> registry; + private final List>> routeEntries; + + private H2Config h2Config; + private Http1Config http1Config; + private HttpProcessor httpProcessor; + private Decorator exchangeHandlerDecorator; public H2TestServer( final IOReactorConfig ioReactorConfig, @@ -70,15 +81,23 @@ public H2TestServer( this.sslContext = sslContext; this.sslSessionInitializer = sslSessionInitializer; this.sslSessionVerifier = sslSessionVerifier; - this.registry = new RequestHandlerRegistry<>(); + this.routeEntries = new ArrayList<>(); } public H2TestServer() throws IOException { this(IOReactorConfig.DEFAULT, null, null, null); } + private void ensureNotRunning() { + Asserts.check(getStatus() == IOReactorStatus.INACTIVE, "Server is already running"); + } + public void register(final String uriPattern, final Supplier supplier) { - registry.register(null, uriPattern, 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( @@ -87,62 +106,131 @@ public void register( register(uriPattern, () -> new BasicServerExchangeHandler<>(requestHandler)); } + /** + * @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; + } + 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 H2Config h2Config) throws Exception { - start(new InternalServerProtocolNegotiationStarter( - httpProcessor != null ? httpProcessor : H2Processors.server(), - new DefaultAsyncResponseExchangeHandlerFactory( - registry, - exchangeHandlerDecorator != null ? exchangeHandlerDecorator : BasicAsyncServerExpectationDecorator::new), - HttpVersionPolicy.FORCE_HTTP_2, - h2Config, - Http1Config.DEFAULT, - CharCodingConfig.DEFAULT, - sslContext, - sslSessionInitializer, - sslSessionVerifier)); - final Future future = listen(new InetSocketAddress(0)); - final ListenerEndpoint listener = future.get(); - return (InetSocketAddress) listener.getAddress(); + 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 Http1Config http1Config) throws Exception { - start(new InternalServerProtocolNegotiationStarter( - httpProcessor != null ? httpProcessor : HttpProcessors.server(), - new DefaultAsyncResponseExchangeHandlerFactory( - registry, - exchangeHandlerDecorator != null ? exchangeHandlerDecorator : BasicAsyncServerExpectationDecorator::new), - HttpVersionPolicy.FORCE_HTTP_1, - H2Config.DEFAULT, - http1Config, - CharCodingConfig.DEFAULT, - sslContext, - sslSessionInitializer, - sslSessionVerifier)); - final Future future = listen(new InetSocketAddress(0)); - final ListenerEndpoint listener = future.get(); - return (InetSocketAddress) listener.getAddress(); + 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 { - return start(null, null, h2Config); + configure(h2Config); + return start(); } + /** + * @deprecated Use {@link #configure(Http1Config)}, {@link #start()}. + */ + @Deprecated public InetSocketAddress start(final Http1Config http1Config) throws Exception { - return start(null, null, http1Config); + configure(http1Config); + return start(); } public InetSocketAddress start() throws Exception { - return start(H2Config.DEFAULT); + if (http1Config != null) { + start(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)); + } else { + start(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)); + } + final Future future = listen(new InetSocketAddress(0)); + final ListenerEndpoint listener = future.get(); + return (InetSocketAddress) listener.getAddress(); } } diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/Http1TestClient.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/Http1TestClient.java index c3de8b26c7..16caea318b 100644 --- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/Http1TestClient.java +++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/Http1TestClient.java @@ -32,9 +32,9 @@ import javax.net.ssl.SSLContext; -import org.apache.hc.core5.concurrent.FutureContribution; import org.apache.hc.core5.concurrent.BasicFuture; import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.concurrent.FutureContribution; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.Http1Config; @@ -42,9 +42,11 @@ import org.apache.hc.core5.http.impl.HttpProcessors; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.reactor.IOReactorConfig; +import org.apache.hc.core5.reactor.IOReactorStatus; import org.apache.hc.core5.reactor.IOSession; 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; public class Http1TestClient extends AsyncRequester { @@ -53,6 +55,9 @@ public class Http1TestClient extends AsyncRequester { private final SSLSessionInitializer sslSessionInitializer; private final SSLSessionVerifier sslSessionVerifier; + private Http1Config http1Config; + private HttpProcessor httpProcessor; + public Http1TestClient( final IOReactorConfig ioReactorConfig, final SSLContext sslContext, @@ -68,11 +73,49 @@ public Http1TestClient() throws IOException { this(IOReactorConfig.DEFAULT, null, null, null); } + private void ensureNotRunning() { + Asserts.check(getStatus() == IOReactorStatus.INACTIVE, "Client is already running"); + } + + /** + * @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; + } + + /** + * @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); + } + + public void start() throws IOException { execute(new InternalClientHttp1EventHandlerFactory( - httpProcessor, + httpProcessor != null ? httpProcessor : HttpProcessors.client(), http1Config, CharCodingConfig.DEFAULT, DefaultConnectionReuseStrategy.INSTANCE, @@ -81,14 +124,6 @@ public void start( sslSessionVerifier)); } - public void start(final Http1Config http1Config) throws IOException { - start(HttpProcessors.client(), http1Config); - } - - public void start() throws IOException { - start(Http1Config.DEFAULT); - } - public Future connect( final HttpHost host, final Timeout timeout, diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/Http1TestServer.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/Http1TestServer.java index bd3cfc6ce6..341be62518 100644 --- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/Http1TestServer.java +++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/Http1TestServer.java @@ -29,6 +29,8 @@ import java.io.IOException; import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Future; import javax.net.ssl.SSLContext; @@ -39,33 +41,40 @@ 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.AsyncServerRequestHandler; 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.protocol.HttpProcessor; -import org.apache.hc.core5.http.protocol.RequestHandlerRegistry; +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.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.Asserts; public class Http1TestServer extends AsyncServer { - private final RequestHandlerRegistry> registry; + private final List>> routeEntries; private final SSLContext sslContext; private final SSLSessionInitializer sslSessionInitializer; private final SSLSessionVerifier sslSessionVerifier; + private Http1Config http1Config; + private HttpProcessor httpProcessor; + private Decorator exchangeHandlerDecorator; + public Http1TestServer( final IOReactorConfig ioReactorConfig, final SSLContext sslContext, final SSLSessionInitializer sslSessionInitializer, final SSLSessionVerifier sslSessionVerifier) throws IOException { super(ioReactorConfig); - this.registry = new RequestHandlerRegistry<>(); + this.routeEntries = new ArrayList<>(); this.sslContext = sslContext; this.sslSessionInitializer = sslSessionInitializer; this.sslSessionVerifier = sslSessionVerifier; @@ -75,8 +84,13 @@ public Http1TestServer() throws IOException { this(IOReactorConfig.DEFAULT, null, null, null); } + private void ensureNotRunning() { + Asserts.check(getStatus() == IOReactorStatus.INACTIVE, "Server is already running"); + } + public void register(final String uriPattern, final Supplier supplier) { - registry.register(null, uriPattern, supplier); + ensureNotRunning(); + routeEntries.add(new RequestRouter.Entry<>(uriPattern, supplier)); } public void register( @@ -85,6 +99,30 @@ public void register( register(uriPattern, () -> new BasicServerExchangeHandler<>(requestHandler)); } + /** + * @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 exchangeHandlerDecorator) { + ensureNotRunning(); + this.exchangeHandlerDecorator = exchangeHandlerDecorator; + } + public InetSocketAddress start(final IOEventHandlerFactory handlerFactory) throws Exception { execute(handlerFactory); final Future future = listen(new InetSocketAddress(0)); @@ -92,14 +130,35 @@ public InetSocketAddress start(final IOEventHandlerFactory handlerFactory) throw return (InetSocketAddress) listener.getAddress(); } + /** + * @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(); + } + + public InetSocketAddress start() throws Exception { return start(new InternalServerHttp1EventHandlerFactory( httpProcessor != null ? httpProcessor : HttpProcessors.server(), new DefaultAsyncResponseExchangeHandlerFactory( - registry, + RequestRouter.create(RequestRouter.LOCAL_AUTHORITY, UriPatternType.URI_PATTERN, routeEntries, RequestRouter.LOCAL_AUTHORITY_RESOLVER, null), exchangeHandlerDecorator != null ? exchangeHandlerDecorator : BasicAsyncServerExpectationDecorator::new), http1Config, CharCodingConfig.DEFAULT, @@ -109,12 +168,4 @@ public InetSocketAddress start( sslSessionVerifier)); } - public InetSocketAddress start(final HttpProcessor httpProcessor, final Http1Config http1Config) throws Exception { - return start(httpProcessor, null, http1Config); - } - - public InetSocketAddress start() throws Exception { - return start(null, null, Http1Config.DEFAULT); - } - } diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalServerHttp1EventHandlerFactory.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalServerHttp1EventHandlerFactory.java index de55f629ee..c7487ec62d 100644 --- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalServerHttp1EventHandlerFactory.java +++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/InternalServerHttp1EventHandlerFactory.java @@ -95,7 +95,7 @@ class InternalServerHttp1EventHandlerFactory implements IOEventHandlerFactory { this.sslSessionInitializer = sslSessionInitializer; this.sslSessionVerifier = sslSessionVerifier; this.requestParserFactory = new DefaultHttpRequestParserFactory(this.http1Config); - this.responseWriterFactory = DefaultHttpResponseWriterFactory.INSTANCE; + this.responseWriterFactory = new DefaultHttpResponseWriterFactory(this.http1Config); } protected ServerHttp1StreamDuplexer createServerHttp1StreamDuplexer( diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/benchmark/BenchmarkToolTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/benchmark/BenchmarkToolTest.java index 5ebce20699..f21d948f68 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/benchmark/BenchmarkToolTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/benchmark/BenchmarkToolTest.java @@ -31,6 +31,7 @@ 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; @@ -40,11 +41,14 @@ 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; @@ -57,7 +61,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -public class BenchmarkToolTest { +class BenchmarkToolTest { public static Stream protocols() { return Stream.of( @@ -69,31 +73,35 @@ public static Stream protocols() { private HttpAsyncServer server; private InetSocketAddress address; - public void setup(final HttpVersionPolicy versionPolicy) throws Exception { + void setup(final HttpVersionPolicy versionPolicy) throws Exception { server = H2ServerBootstrap.bootstrap() - .register("/", new AsyncServerRequestHandler>() { + .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 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); - } + @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(); @@ -103,7 +111,7 @@ public void handle( } @AfterEach - public void shutdown() throws Exception { + void shutdown() { if (server != null) { server.close(CloseMode.IMMEDIATE); } @@ -112,7 +120,7 @@ public void shutdown() throws Exception { @ParameterizedTest(name = "{0}") @MethodSource("protocols") - public void testBasics(final HttpVersionPolicy versionPolicy) throws Exception { + void testBasics(final HttpVersionPolicy versionPolicy) throws Exception { setup(versionPolicy); final BenchmarkConfig config = BenchmarkConfig.custom() .setKeepAlive(true) diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/benchmark/ResultFormatterTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/benchmark/ResultFormatterTest.java index abb3e6b802..e9a19add0c 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/benchmark/ResultFormatterTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/benchmark/ResultFormatterTest.java @@ -36,10 +36,10 @@ import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; -public class ResultFormatterTest { +class ResultFormatterTest { @Test - public void testBasics() throws Exception { + void testBasics() throws Exception { final Results results = new Results( "TestServer/1.1", HttpVersion.HTTP_1_1, diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicAuthenticationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicAuthenticationTest.java index a752b607ee..ec0d142ba1 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicAuthenticationTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicAuthenticationTest.java @@ -47,6 +47,8 @@ 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; @@ -56,14 +58,14 @@ 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.classic.extension.HttpRequesterResource; -import org.apache.hc.core5.testing.classic.extension.HttpServerResource; +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; -public abstract class ClassicAuthenticationTest { +abstract class ClassicAuthenticationTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @@ -78,7 +80,10 @@ public ClassicAuthenticationTest(final Boolean respondImmediately) { SocketConfig.custom() .setSoTimeout(TIMEOUT) .build()) - .register("*", new EchoHandler()) + .setRequestRouter(RequestRouter.builder() + .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", new EchoHandler()) + .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) + .build()) .replaceFilter(StandardFilter.EXPECT_CONTINUE.name(), new AbstractHttpServerAuthFilter(respondImmediately) { @Override @@ -119,7 +124,7 @@ protected HttpEntity generateResponseContent(final HttpResponse unauthorized) { } @Test - public void testGetRequestAuthentication() throws Exception { + void testGetRequestAuthentication() throws Exception { final HttpServer server = serverResource.start(); final HttpRequester requester = clientResource.start(); @@ -141,7 +146,7 @@ public void testGetRequestAuthentication() throws Exception { } @Test - public void testPostRequestAuthentication() throws Exception { + void testPostRequestAuthentication() throws Exception { final HttpServer server = serverResource.start(); final HttpRequester requester = clientResource.start(); @@ -170,7 +175,7 @@ public void testPostRequestAuthentication() throws Exception { } @Test - public void testPostRequestAuthenticationNoExpectContinue() throws Exception { + void testPostRequestAuthenticationNoExpectContinue() throws Exception { final HttpServer server = serverResource.start(); final HttpRequester requester = clientResource.start(); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicHttp1CoreTransportTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicHttp1CoreTransportTest.java index c004138171..a9e5b98577 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicHttp1CoreTransportTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicHttp1CoreTransportTest.java @@ -37,14 +37,16 @@ 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.classic.extension.HttpRequesterResource; -import org.apache.hc.core5.testing.classic.extension.HttpServerResource; +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; -public abstract class ClassicHttp1CoreTransportTest extends ClassicHttpCoreTransportTest { +abstract class ClassicHttp1CoreTransportTest extends ClassicHttpCoreTransportTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @@ -59,7 +61,10 @@ public ClassicHttp1CoreTransportTest(final URIScheme scheme) { .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .build()) - .register("*", new EchoHandler()) + .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() { diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicHttp1SocksProxyCoreTransportTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicHttp1SocksProxyCoreTransportTest.java index 89167fdebd..deb3598ae5 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicHttp1SocksProxyCoreTransportTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicHttp1SocksProxyCoreTransportTest.java @@ -37,16 +37,18 @@ 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.classic.extension.HttpRequesterResource; -import org.apache.hc.core5.testing.classic.extension.HttpServerResource; import org.apache.hc.core5.testing.extension.SocksProxyResource; +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.Order; import org.junit.jupiter.api.extension.RegisterExtension; -public abstract class ClassicHttp1SocksProxyCoreTransportTest extends ClassicHttpCoreTransportTest { +abstract class ClassicHttp1SocksProxyCoreTransportTest extends ClassicHttpCoreTransportTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @@ -65,7 +67,10 @@ public ClassicHttp1SocksProxyCoreTransportTest(final URIScheme scheme) { .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .build()) - .register("*", new EchoHandler()) + .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() { diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicHttpCoreTransportTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicHttpCoreTransportTest.java index 8d3dd0cbdd..07800628fb 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicHttpCoreTransportTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicHttpCoreTransportTest.java @@ -30,6 +30,12 @@ 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; @@ -46,9 +52,10 @@ 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; -public abstract class ClassicHttpCoreTransportTest { +abstract class ClassicHttpCoreTransportTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @@ -63,7 +70,7 @@ public ClassicHttpCoreTransportTest(final URIScheme scheme) { abstract HttpRequester clientStart() throws IOException; @Test - public void testSequentialRequests() throws Exception { + void testSequentialRequests() throws Exception { final HttpServer server = serverStart(); final HttpRequester requester = clientStart(); @@ -93,7 +100,7 @@ public void testSequentialRequests() throws Exception { } @Test - public void testSequentialRequestsNonPersistentConnection() throws Exception { + void testSequentialRequestsNonPersistentConnection() throws Exception { final HttpServer server = serverStart(); final HttpRequester requester = clientStart(); @@ -122,4 +129,51 @@ public void testSequentialRequestsNonPersistentConnection() throws Exception { } } + @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; + } + } + } diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicIntegrationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicIntegrationTest.java index f9a77dfa04..e92582efd6 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicIntegrationTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicIntegrationTest.java @@ -49,6 +49,7 @@ 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.io.entity.AbstractHttpEntity; @@ -66,13 +67,13 @@ import org.apache.hc.core5.http.protocol.RequestExpectContinue; import org.apache.hc.core5.http.protocol.RequestTargetHost; import org.apache.hc.core5.http.protocol.RequestUserAgent; -import org.apache.hc.core5.testing.classic.extension.ClassicTestResources; +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; -public abstract class ClassicIntegrationTest { +abstract class ClassicIntegrationTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @@ -89,7 +90,7 @@ public ClassicIntegrationTest(final URIScheme scheme) { * This test case executes a series of simple GET requests */ @Test - public void testSimpleBasicHttpRequests() throws Exception { + void testSimpleBasicHttpRequests() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); @@ -108,7 +109,7 @@ public void testSimpleBasicHttpRequests() throws Exception { // Initialize the server-side request handler - server.registerHandler("*", (request, response, context) -> { + server.register("*", (request, response, context) -> { String s = request.getPath(); if (s.startsWith("/?")) { @@ -145,7 +146,7 @@ public void testSimpleBasicHttpRequests() throws Exception { * delimited content. */ @Test - public void testSimpleHttpPostsWithContentLength() throws Exception { + void testSimpleHttpPostsWithContentLength() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); @@ -163,7 +164,7 @@ public void testSimpleHttpPostsWithContentLength() throws Exception { } // Initialize the server-side request handler - server.registerHandler("*", (request, response, context) -> { + server.register("*", (request, response, context) -> { final HttpEntity entity = request.getEntity(); if (entity != null) { @@ -200,7 +201,7 @@ public void testSimpleHttpPostsWithContentLength() throws Exception { * coded content content. */ @Test - public void testSimpleHttpPostsChunked() throws Exception { + void testSimpleHttpPostsChunked() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); @@ -218,7 +219,7 @@ public void testSimpleHttpPostsChunked() throws Exception { } // Initialize the server-side request handler - server.registerHandler("*", (request, response, context) -> { + server.register("*", (request, response, context) -> { final HttpEntity entity = request.getEntity(); if (entity != null) { @@ -254,7 +255,7 @@ public void testSimpleHttpPostsChunked() throws Exception { * This test case executes a series of simple HTTP/1.0 POST requests. */ @Test - public void testSimpleHttpPostsHTTP10() throws Exception { + void testSimpleHttpPostsHTTP10() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); @@ -272,16 +273,13 @@ public void testSimpleHttpPostsHTTP10() throws Exception { } // Initialize the server-side request handler - server.registerHandler("*", (request, response, context) -> { + 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)); } - if (HttpVersion.HTTP_1_0.equals(request.getVersion())) { - response.addHeader("Version", "1.0"); - } }); server.start(); @@ -298,10 +296,6 @@ public void testSimpleHttpPostsHTTP10() throws Exception { post.setEntity(new ByteArrayEntity(data, null)); try (final ClassicHttpResponse response = client.execute(host, post, context)) { - Assertions.assertEquals(HttpVersion.HTTP_1_1, response.getVersion()); - final Header h1 = response.getFirstHeader("Version"); - Assertions.assertNotNull(h1); - Assertions.assertEquals("1.0", h1.getValue()); final byte[] received = EntityUtils.toByteArray(response.getEntity()); final byte[] expected = testData.get(r); @@ -313,12 +307,38 @@ public void testSimpleHttpPostsHTTP10() throws Exception { } } + /** + * 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 - public void testHttpPostsWithExpectContinue() throws Exception { + void testHttpPostsWithExpectContinue() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); @@ -336,7 +356,7 @@ public void testHttpPostsWithExpectContinue() throws Exception { } // Initialize the server-side request handler - server.registerHandler("*", (request, response, context) -> { + server.register("*", (request, response, context) -> { final HttpEntity entity = request.getEntity(); if (entity != null) { @@ -373,16 +393,15 @@ public void testHttpPostsWithExpectContinue() throws Exception { * meet the target server expectations. */ @Test - public void testHttpPostsWithExpectationVerification() throws Exception { + 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.registerHandler("*", (request, response, context) -> response.setEntity(new StringEntity("No content"))); - - server.start(null, null, handler -> new BasicHttpServerExpectationDecorator(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) { @@ -406,6 +425,7 @@ protected ClassicHttpResponse verify(final ClassicHttpRequest request, final Htt } }); + server.start(); client.start(); final HttpCoreContext context = HttpCoreContext.create(); @@ -442,7 +462,7 @@ static class RepeatingEntity extends AbstractHttpEntity { 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.US_ASCII; + final Charset cs = charset != null ? charset : StandardCharsets.UTF_8; this.raw = content.getBytes(cs); this.n = n; } @@ -484,7 +504,7 @@ public void close() throws IOException { } @Test - public void testHttpContent() throws Exception { + void testHttpContent() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); @@ -509,7 +529,7 @@ public void testHttpContent() throws Exception { }; // Initialize the server-side request handler - server.registerHandler("*", (request, response, context) -> { + server.register("*", (request, response, context) -> { int n = 1; String s = request.getPath(); @@ -531,7 +551,7 @@ public void testHttpContent() throws Exception { if (entity != null) { final String line = EntityUtils.toString(entity); final ContentType contentType = ContentType.parse(entity.getContentType()); - final Charset charset = ContentType.getCharset(contentType, StandardCharsets.ISO_8859_1); + final Charset charset = ContentType.getCharset(contentType, StandardCharsets.UTF_8); response.setEntity(new RepeatingEntity(line, charset, n, n % 2 == 0)); } }); @@ -553,7 +573,7 @@ public void testHttpContent() throws Exception { Assertions.assertNotNull(entity); final InputStream inStream = entity.getContent(); final ContentType contentType = ContentType.parse(entity.getContentType()); - final Charset charset = ContentType.getCharset(contentType, StandardCharsets.ISO_8859_1); + final Charset charset = ContentType.getCharset(contentType, StandardCharsets.UTF_8); Assertions.assertNotNull(inStream); final BufferedReader reader = new BufferedReader(new InputStreamReader(inStream, charset)); @@ -570,11 +590,11 @@ public void testHttpContent() throws Exception { } @Test - public void testHttpPostNoEntity() throws Exception { + void testHttpPostNoEntity() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); - server.registerHandler("*", (request, response, context) -> { + server.register("*", (request, response, context) -> { final HttpEntity entity = request.getEntity(); if (entity != null) { @@ -600,11 +620,11 @@ public void testHttpPostNoEntity() throws Exception { } @Test - public void testHttpPostNoContentLength() throws Exception { + void testHttpPostNoContentLength() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); - server.registerHandler("*", (request, response, context) -> { + server.register("*", (request, response, context) -> { final HttpEntity entity = request.getEntity(); if (entity != null) { @@ -614,11 +634,12 @@ public void testHttpPostNoContentLength() throws Exception { }); server.start(); - client.start(new DefaultHttpProcessor( + client.configure(new DefaultHttpProcessor( 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()); @@ -634,11 +655,11 @@ public void testHttpPostNoContentLength() throws Exception { } @Test - public void testHttpPostIdentity() throws Exception { + void testHttpPostIdentity() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); - server.registerHandler("*", (request, response, context) -> { + server.register("*", (request, response, context) -> { final HttpEntity entity = request.getEntity(); if (entity != null) { @@ -648,12 +669,13 @@ public void testHttpPostIdentity() throws Exception { }); server.start(); - client.start(new DefaultHttpProcessor( + 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()); @@ -667,14 +689,14 @@ public void testHttpPostIdentity() throws Exception { } @Test - public void testNoContentResponse() throws Exception { + 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.registerHandler("*", (request, response, context) -> response.setCode(HttpStatus.SC_NO_CONTENT)); + server.register("*", (request, response, context) -> response.setCode(HttpStatus.SC_NO_CONTENT)); server.start(); client.start(); @@ -691,47 +713,17 @@ public void testNoContentResponse() throws Exception { } @Test - public void testAbsentHostHeader() throws Exception { + void testHeaderTooLarge() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); - // Initialize the server-side request handler - server.registerHandler("*", (request, response, context) -> response.setEntity(new StringEntity("All is well", StandardCharsets.US_ASCII))); - - server.start(); - client.start(new DefaultHttpProcessor(RequestContent.INSTANCE, new RequestConnControl())); - - final HttpCoreContext context = HttpCoreContext.create(); - final HttpHost host = new HttpHost(scheme.id, "localhost", server.getPort()); - - final BasicClassicHttpRequest get1 = new BasicClassicHttpRequest(Method.GET, "/"); - get1.setVersion(HttpVersion.HTTP_1_0); - try (final ClassicHttpResponse response1 = client.execute(host, get1, context)) { - Assertions.assertEquals(200, response1.getCode()); - EntityUtils.consume(response1.getEntity()); - } - - final BasicClassicHttpRequest get2 = new BasicClassicHttpRequest(Method.GET, "/"); - try (final ClassicHttpResponse response2 = client.execute(host, get2, context)) { - Assertions.assertEquals(400, response2.getCode()); - EntityUtils.consume(response2.getEntity()); - } - } - - @Test - public void testHeaderTooLarge() throws Exception { - final ClassicTestServer server = testResources.server(); - final ClassicTestClient client = testResources.client(); - - server.registerHandler("*", (request, response, context) -> + server.register("*", (request, response, context) -> response.setEntity(new StringEntity("All is well", StandardCharsets.US_ASCII))); - - server.start( + server.configure( Http1Config.custom() .setMaxLineLength(100) - .build(), - null, - null); + .build()); + server.start(); client.start(); final HttpCoreContext context = HttpCoreContext.create(); @@ -747,21 +739,21 @@ public void testHeaderTooLarge() throws Exception { } @Test - public void testHeaderTooLargePost() throws Exception { + void testHeaderTooLargePost() throws Exception { final ClassicTestServer server = testResources.server(); final ClassicTestClient client = testResources.client(); - server.registerHandler("*", (request, response, context) -> + server.register("*", (request, response, context) -> response.setEntity(new StringEntity("All is well", StandardCharsets.US_ASCII))); - server.start( + server.configure( Http1Config.custom() .setMaxLineLength(100) - .build(), - null, - null); - client.start( + .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()); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicIntegrationTests.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicIntegrationTests.java index e791a1a143..fa90c09177 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicIntegrationTests.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicIntegrationTests.java @@ -31,11 +31,11 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; -public class ClassicIntegrationTests { +class ClassicIntegrationTests { @Nested @DisplayName("Core transport") - public class CoreTransport extends ClassicHttp1CoreTransportTest { + class CoreTransport extends ClassicHttp1CoreTransportTest { public CoreTransport() { super(URIScheme.HTTP); @@ -45,7 +45,7 @@ public CoreTransport() { @Nested @DisplayName("Core transport (TLS)") - public class CoreTransportTls extends ClassicHttp1CoreTransportTest { + class CoreTransportTls extends ClassicHttp1CoreTransportTest { public CoreTransportTls() { super(URIScheme.HTTPS); @@ -55,7 +55,7 @@ public CoreTransportTls() { @Nested @DisplayName("Authentication") - public class Authentication extends ClassicAuthenticationTest { + class Authentication extends ClassicAuthenticationTest { public Authentication() { super(false); @@ -65,7 +65,7 @@ public Authentication() { @Nested @DisplayName("Authentication (immediate response)") - public class AuthenticationImmediateResponse extends ClassicAuthenticationTest { + class AuthenticationImmediateResponse extends ClassicAuthenticationTest { public AuthenticationImmediateResponse() { super(true); @@ -75,7 +75,7 @@ public AuthenticationImmediateResponse() { @Nested @DisplayName("Out-of-order response monitoring") - public class MonitoringResponseOutOfOrderStrategy extends MonitoringResponseOutOfOrderStrategyIntegrationTest { + class MonitoringResponseOutOfOrderStrategy extends MonitoringResponseOutOfOrderStrategyIntegrationTest { public MonitoringResponseOutOfOrderStrategy() { super(URIScheme.HTTP); @@ -85,7 +85,7 @@ public MonitoringResponseOutOfOrderStrategy() { @Nested @DisplayName("Out-of-order response monitoring (TLS)") - public class MonitoringResponseOutOfOrderStrategyTls extends MonitoringResponseOutOfOrderStrategyIntegrationTest { + class MonitoringResponseOutOfOrderStrategyTls extends MonitoringResponseOutOfOrderStrategyIntegrationTest { public MonitoringResponseOutOfOrderStrategyTls() { super(URIScheme.HTTPS); @@ -95,7 +95,7 @@ public MonitoringResponseOutOfOrderStrategyTls() { @Nested @DisplayName("Server filters") - public class HttpFilters extends ClassicServerBootstrapFilterTest { + class HttpFilters extends ClassicServerBootstrapFilterTest { public HttpFilters() { super(URIScheme.HTTP); @@ -105,7 +105,7 @@ public HttpFilters() { @Nested @DisplayName("Core transport (SOCKS)") - public class CoreTransportSocksProxy extends ClassicHttp1SocksProxyCoreTransportTest { + class CoreTransportSocksProxy extends ClassicHttp1SocksProxyCoreTransportTest { public CoreTransportSocksProxy() { super(URIScheme.HTTP); @@ -115,7 +115,7 @@ public CoreTransportSocksProxy() { @Nested @DisplayName("Core transport (TLS, SOCKS)") - public class CoreTransportSocksProxyTls extends ClassicHttp1SocksProxyCoreTransportTest { + class CoreTransportSocksProxyTls extends ClassicHttp1SocksProxyCoreTransportTest { public CoreTransportSocksProxyTls() { super(URIScheme.HTTPS); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicProtocolTests.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicProtocolTests.java index c3f5eaac12..ec21ed3726 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicProtocolTests.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicProtocolTests.java @@ -31,11 +31,11 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; -public class ClassicProtocolTests { +class ClassicProtocolTests { @Nested @DisplayName("Classic HTTP/1.1 (plain)") - public class Http1 extends ClassicIntegrationTest { + class Http1 extends ClassicIntegrationTest { public Http1() { super(URIScheme.HTTP); @@ -45,7 +45,7 @@ public Http1() { @Nested @DisplayName("Classic HTTP/1.1 (TLS)") - public class Http1Tls extends ClassicIntegrationTest { + class Http1Tls extends ClassicIntegrationTest { public Http1Tls() { super(URIScheme.HTTPS); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicServerBootstrapFilterTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicServerBootstrapFilterTest.java index 16fac4a845..ef191c3c8f 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicServerBootstrapFilterTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicServerBootstrapFilterTest.java @@ -42,19 +42,21 @@ 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.classic.extension.HttpRequesterResource; -import org.apache.hc.core5.testing.classic.extension.HttpServerResource; +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; -public abstract class ClassicServerBootstrapFilterTest { +abstract class ClassicServerBootstrapFilterTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @@ -72,7 +74,10 @@ public ClassicServerBootstrapFilterTest(final URIScheme scheme) { .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) .build()) - .register("*", new EchoHandler()) + .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() { @@ -98,7 +103,7 @@ public void submitResponse( } @Test - public void testFilters() throws Exception { + void testFilters() throws Exception { final HttpServer server = serverResource.start(); final HttpRequester requester = clientResource.start(); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicTLSIntegrationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicTLSIntegrationTest.java index 474ede3384..6daf502959 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicTLSIntegrationTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicTLSIntegrationTest.java @@ -30,8 +30,10 @@ 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; @@ -45,12 +47,13 @@ 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.BasicHttpContext; -import org.apache.hc.core5.http.protocol.HttpContext; +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; @@ -63,9 +66,9 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.RegisterExtension; -public class ClassicTLSIntegrationTest { +class ClassicTLSIntegrationTest { - private static final Timeout TIMEOUT = Timeout.ofMinutes(1); + private static final Timeout TIMEOUT = Timeout.ofSeconds(30); private HttpServer server; @@ -102,7 +105,7 @@ public void afterEach(final ExtensionContext context) throws Exception { }; @Test - public void testTLSSuccess() throws Exception { + void testTLSSuccess() throws Exception { server = ServerBootstrap.bootstrap() .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) @@ -110,7 +113,10 @@ public void testTLSSuccess() throws Exception { .setSslContext(SSLTestContexts.createServerSSLContext()) .setExceptionListener(LoggingExceptionListener.INSTANCE) .setStreamListener(LoggingHttp1StreamListener.INSTANCE) - .register("*", new EchoHandler()) + .setRequestRouter(RequestRouter.builder() + .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", new EchoHandler()) + .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) + .build()) .create(); server.start(); @@ -126,7 +132,7 @@ public void testTLSSuccess() throws Exception { .setConnPoolListener(LoggingConnPoolListener.INSTANCE) .create(); - final HttpContext context = new BasicHttpContext(); + 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)); @@ -144,7 +150,7 @@ public void testTLSSuccess() throws Exception { } @Test - public void testTLSTrustFailure() throws Exception { + void testTLSTrustFailure() throws Exception { server = ServerBootstrap.bootstrap() .setSocketConfig(SocketConfig.custom() .setSoTimeout(TIMEOUT) @@ -165,7 +171,7 @@ public void testTLSTrustFailure() throws Exception { .setConnPoolListener(LoggingConnPoolListener.INSTANCE) .create(); - final HttpContext context = new BasicHttpContext(); + 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)); @@ -177,7 +183,7 @@ public void testTLSTrustFailure() throws Exception { } @Test - public void testTLSClientAuthFailure() throws Exception { + void testTLSClientAuthFailure() throws Exception { server = ServerBootstrap.bootstrap() .setSslContext(SSLTestContexts.createClientSSLContext()) .setSocketConfig(SocketConfig.custom() @@ -200,7 +206,7 @@ public void testTLSClientAuthFailure() throws Exception { .setConnPoolListener(LoggingConnPoolListener.INSTANCE) .create(); - final HttpContext context = new BasicHttpContext(); + 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)); @@ -212,10 +218,11 @@ public void testTLSClientAuthFailure() throws Exception { } @Test - public void testSSLDisabledByDefault() throws Exception { + void testSSLDisabledByDefault() throws Exception { server = ServerBootstrap.bootstrap() .setSslContext(SSLTestContexts.createServerSSLContext()) .setSslSetupHandler(sslParameters -> sslParameters.setProtocols(new String[]{"SSLv3"})) + .setRequestRouter((r, c) -> null) .create(); server.start(); @@ -228,7 +235,7 @@ public void testSSLDisabledByDefault() throws Exception { .setConnPoolListener(LoggingConnPoolListener.INSTANCE) .create(); - final HttpContext context = new BasicHttpContext(); + 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)); @@ -240,7 +247,7 @@ public void testSSLDisabledByDefault() throws Exception { } @Test - public void testWeakCiphersDisabledByDefault() throws Exception { + void testWeakCiphersDisabledByDefault() throws Exception { requester = RequesterBootstrap.bootstrap() .setSslContext(SSLTestContexts.createClientSSLContext()) @@ -271,13 +278,14 @@ public void testWeakCiphersDisabledByDefault() throws Exception { for (final String cipherSuite : weakCiphersSuites) { server = ServerBootstrap.bootstrap() .setSslContext(SSLTestContexts.createServerSSLContext()) - .setSslSetupHandler(sslParameters -> sslParameters.setProtocols(new String[]{cipherSuite})) + .setRequestRouter((r, c) -> null) + .setSslSetupHandler(sslParameters -> sslParameters.setCipherSuites(new String[]{cipherSuite})) .create(); Assertions.assertThrows(Exception.class, () -> { try { server.start(); - final HttpContext context = new BasicHttpContext(); + 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)); @@ -291,4 +299,39 @@ public void testWeakCiphersDisabledByDefault() throws Exception { } } + @Test + void testHostNameVerification() throws Exception { + server = ServerBootstrap.bootstrap() + .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.getLocalHost(), "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.getLocalHost(), "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()); + } + }); + } + } diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/MonitoringResponseOutOfOrderStrategyIntegrationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/MonitoringResponseOutOfOrderStrategyIntegrationTest.java index 38ce0cca89..38d25ea63f 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/MonitoringResponseOutOfOrderStrategyIntegrationTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/MonitoringResponseOutOfOrderStrategyIntegrationTest.java @@ -42,19 +42,21 @@ 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.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.protocol.HttpCoreContext; -import org.apache.hc.core5.testing.classic.extension.HttpRequesterResource; -import org.apache.hc.core5.testing.classic.extension.HttpServerResource; +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.extension.RegisterExtension; -public abstract class MonitoringResponseOutOfOrderStrategyIntegrationTest { +abstract class MonitoringResponseOutOfOrderStrategyIntegrationTest { // Use a 16k buffer for consistent results across systems private static final int BUFFER_SIZE = 16 * 1024; @@ -78,10 +80,13 @@ public MonitoringResponseOutOfOrderStrategyIntegrationTest(final URIScheme schem .setRcvBufSize(BUFFER_SIZE) .setSoKeepAlive(false) .build()) - .register("*", (request, response, context) -> { - response.setCode(400); - response.setEntity(new AllOnesHttpEntity(200000)); - })); + .setRequestRouter(RequestRouter.builder() + .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", (request, response, context) -> { + response.setCode(400); + response.setEntity(new AllOnesHttpEntity(200000)); + }) + .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) + .build())); this.clientResource = new HttpRequesterResource(bootstrap -> bootstrap .setSocketConfig(SocketConfig.custom() @@ -97,7 +102,7 @@ public MonitoringResponseOutOfOrderStrategyIntegrationTest(final URIScheme schem @Test @org.junit.jupiter.api.Timeout(value = 1, unit = TimeUnit.MINUTES)// Failures may hang - public void testResponseOutOfOrderWithDefaultStrategy() throws Exception { + void testResponseOutOfOrderWithDefaultStrategy() throws Exception { final HttpServer server = serverResource.start(); final HttpRequester requester = clientResource.start(); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/H2CompatibilityTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/H2CompatibilityTest.java index b82ad06bb4..e2ca5c66fa 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/H2CompatibilityTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/H2CompatibilityTest.java @@ -96,7 +96,7 @@ public static void main(final String... args) throws Exception { } } - H2CompatibilityTest() throws Exception { + H2CompatibilityTest() { this.client = H2RequesterBootstrap.bootstrap() .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(TIMEOUT) @@ -113,11 +113,11 @@ public static void main(final String... args) throws Exception { .create(); } - void start() throws Exception { + void start() { client.start(); } - void shutdown() throws Exception { + void shutdown() { client.close(CloseMode.GRACEFUL); } @@ -285,24 +285,16 @@ void executeHttpBin(final HttpHost target) throws Exception { System.out.println("*** httpbin.org HTTP/1.1 simple request execution ***"); final List> requestMessages = Arrays.asList( - new Message<>( - new BasicHttpRequest(Method.GET, target, "/headers"), - null), + 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"), - null), - new Message<>( - new BasicHttpRequest(Method.GET, target, "/bytes/20000"), - null), - new Message<>( - new BasicHttpRequest(Method.GET, target, "/delay/2"), - null), + 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)), diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/HttpBinIT.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/HttpBinIT.java index 60479b7acc..688f49298a 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/HttpBinIT.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/compatibility/http2/HttpBinIT.java @@ -32,22 +32,22 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class HttpBinIT { +class HttpBinIT { private H2CompatibilityTest h2CompatibilityTest; @BeforeEach - public void start() throws Exception { + void start() throws Exception { h2CompatibilityTest = new H2CompatibilityTest(); h2CompatibilityTest.start(); } @AfterEach - public void shutdown() throws Exception { + void shutdown() throws Exception { h2CompatibilityTest.shutdown(); } @Test - public void executeHttpBin() throws Exception { + void executeHttpBin() throws Exception { h2CompatibilityTest.executeHttpBin(new HttpHost("http", "localhost", 8082)); } } diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/extension/ClassicTestResources.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/classic/ClassicTestResources.java similarity index 98% rename from httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/extension/ClassicTestResources.java rename to httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/classic/ClassicTestResources.java index 6acbc769c3..ad80ee7df4 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/extension/ClassicTestResources.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/classic/ClassicTestResources.java @@ -25,7 +25,7 @@ * */ -package org.apache.hc.core5.testing.classic.extension; +package org.apache.hc.core5.testing.extension.classic; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.io.SocketConfig; diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/extension/HttpRequesterResource.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/classic/HttpRequesterResource.java similarity index 98% rename from httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/extension/HttpRequesterResource.java rename to httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/classic/HttpRequesterResource.java index e9185815e4..b5d6be5312 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/extension/HttpRequesterResource.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/classic/HttpRequesterResource.java @@ -25,7 +25,7 @@ * */ -package org.apache.hc.core5.testing.classic.extension; +package org.apache.hc.core5.testing.extension.classic; import java.util.function.Consumer; diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/extension/HttpServerResource.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/classic/HttpServerResource.java similarity index 98% rename from httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/extension/HttpServerResource.java rename to httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/classic/HttpServerResource.java index 3c6d04690f..ec961f1046 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/extension/HttpServerResource.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/classic/HttpServerResource.java @@ -25,7 +25,7 @@ * */ -package org.apache.hc.core5.testing.classic.extension; +package org.apache.hc.core5.testing.extension.classic; import java.io.IOException; import java.util.function.Consumer; diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/H2AsyncRequesterResource.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/H2AsyncRequesterResource.java similarity index 98% rename from httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/H2AsyncRequesterResource.java rename to httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/H2AsyncRequesterResource.java index c4a36a98c2..6ed7cfc9f0 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/H2AsyncRequesterResource.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/H2AsyncRequesterResource.java @@ -25,7 +25,7 @@ * */ -package org.apache.hc.core5.testing.nio.extension; +package org.apache.hc.core5.testing.extension.nio; import java.util.function.Consumer; diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/H2AsyncServerResource.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/H2AsyncServerResource.java similarity index 96% rename from httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/H2AsyncServerResource.java rename to httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/H2AsyncServerResource.java index 9f87d05f19..188eac814f 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/H2AsyncServerResource.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/H2AsyncServerResource.java @@ -25,9 +25,8 @@ * */ -package org.apache.hc.core5.testing.nio.extension; +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; @@ -85,7 +84,7 @@ public void afterEach(final ExtensionContext extensionContext) throws Exception } } - public HttpAsyncServer start() throws IOException { + public HttpAsyncServer start() { Assertions.assertNotNull(server); server.start(); return server; diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/H2MultiplexingRequesterResource.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/H2MultiplexingRequesterResource.java similarity index 98% rename from httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/H2MultiplexingRequesterResource.java rename to httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/H2MultiplexingRequesterResource.java index 8b9ad8e9c5..1739ef893d 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/H2MultiplexingRequesterResource.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/H2MultiplexingRequesterResource.java @@ -25,7 +25,7 @@ * */ -package org.apache.hc.core5.testing.nio.extension; +package org.apache.hc.core5.testing.extension.nio; import java.util.function.Consumer; diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/H2TestResources.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/H2TestResources.java similarity index 98% rename from httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/H2TestResources.java rename to httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/H2TestResources.java index 113ca30066..e6180ab900 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/H2TestResources.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/H2TestResources.java @@ -25,7 +25,7 @@ * */ -package org.apache.hc.core5.testing.nio.extension; +package org.apache.hc.core5.testing.extension.nio; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.reactor.IOReactorConfig; diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/Http1TestResources.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/Http1TestResources.java similarity index 98% rename from httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/Http1TestResources.java rename to httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/Http1TestResources.java index 04ecfad819..155089543d 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/Http1TestResources.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/Http1TestResources.java @@ -25,7 +25,7 @@ * */ -package org.apache.hc.core5.testing.nio.extension; +package org.apache.hc.core5.testing.extension.nio; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.reactor.IOReactorConfig; diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/HttpAsyncRequesterResource.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/HttpAsyncRequesterResource.java similarity index 98% rename from httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/HttpAsyncRequesterResource.java rename to httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/HttpAsyncRequesterResource.java index 02d035ed00..18a5ac67f1 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/HttpAsyncRequesterResource.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/HttpAsyncRequesterResource.java @@ -25,7 +25,7 @@ * */ -package org.apache.hc.core5.testing.nio.extension; +package org.apache.hc.core5.testing.extension.nio; import java.util.function.Consumer; diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/HttpAsyncServerResource.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/HttpAsyncServerResource.java similarity index 96% rename from httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/HttpAsyncServerResource.java rename to httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/HttpAsyncServerResource.java index fb76d7e24f..d7ee307e4d 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/extension/HttpAsyncServerResource.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/nio/HttpAsyncServerResource.java @@ -25,9 +25,8 @@ * */ -package org.apache.hc.core5.testing.nio.extension; +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; @@ -83,7 +82,7 @@ public void afterEach(final ExtensionContext extensionContext) throws Exception } } - public HttpAsyncServer start() throws IOException { + public HttpAsyncServer start() { Assertions.assertNotNull(server); server.start(); return server; diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestClassicTestClientTestingAdapter.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestClassicTestClientTestingAdapter.java index ee927b15bf..a6856ab350 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestClassicTestClientTestingAdapter.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestClassicTestClientTestingAdapter.java @@ -54,27 +54,27 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -public class TestClassicTestClientTestingAdapter { +class TestClassicTestClientTestingAdapter { private static final String ECHO_PATH = "echo/something"; private static final String CUSTOM_PATH = "custom/something"; private ClassicTestServer server; - @BeforeEach - public void initServer() throws Exception { + @BeforeEach + void initServer() { this.server = new ClassicTestServer(SocketConfig.custom() .setSoTimeout(5, TimeUnit.SECONDS).build()); } @AfterEach - public void shutDownServer() throws Exception { + void shutDownServer() { if (this.server != null) { this.server.shutdown(CloseMode.IMMEDIATE); } } @Test - public void nullDefaultURI() throws Exception { + void nullDefaultURI() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final String defaultURI = null; @@ -87,7 +87,7 @@ public void nullDefaultURI() throws Exception { } @Test - public void nullRequest() throws Exception { + void nullRequest() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final String defaultURI = ""; @@ -100,7 +100,7 @@ public void nullRequest() throws Exception { } @Test - public void nullRequestHandler() throws Exception { + void nullRequestHandler() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final String defaultURI = ""; @@ -113,7 +113,7 @@ public void nullRequestHandler() throws Exception { } @Test - public void nullResponseExpectations() throws Exception { + void nullResponseExpectations() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final String defaultURI = ""; @@ -126,7 +126,7 @@ public void nullResponseExpectations() throws Exception { } @Test - public void noPath() throws Exception { + void noPath() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final String defaultURI = ""; @@ -139,7 +139,7 @@ public void noPath() throws Exception { } @Test - public void noMethod() throws Exception { + void noMethod() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final String defaultURI = ""; @@ -155,7 +155,7 @@ public void noMethod() throws Exception { } @Test - public void invalidMethod() throws Exception { + void invalidMethod() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final String defaultURI = ""; @@ -172,11 +172,11 @@ public void invalidMethod() throws Exception { } @Test - public void withLiveServerEcho() throws Exception { + void withLiveServerEcho() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); // Initialize the server-side request handler - server.registerHandler("/echo/*", new EchoHandler()); + server.register("/echo/*", new EchoHandler()); this.server.start(); @@ -209,7 +209,7 @@ public void withLiveServerEcho() throws Exception { } @Test - public void withLiveServerCustomRequestHandler() throws Exception { + void withLiveServerCustomRequestHandler() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final TestingFrameworkRequestHandler requestHandler = new TestingFrameworkRequestHandler() { @@ -217,13 +217,13 @@ public void withLiveServerCustomRequestHandler() throws Exception { public void handle(final ClassicHttpRequest request, final ClassicHttpResponse response, final HttpContext context) throws HttpException, IOException { try { - Assertions.assertEquals("method not expected", "junk", request.getMethod()); + Assertions.assertEquals("junk", request.getMethod(), "method not expected"); } catch (final Throwable t) { thrown = t; } } }; - server.registerHandler("/custom/*", requestHandler); + server.register("/custom/*", requestHandler); this.server.start(); final HttpHost target = new HttpHost("localhost", this.server.getPort()); @@ -241,7 +241,7 @@ public void handle(final ClassicHttpRequest request, final ClassicHttpResponse r } @Test - public void modifyRequest() { + void modifyRequest() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final Map request = new HashMap<>(); @@ -251,7 +251,7 @@ public void modifyRequest() { } @Test - public void modifyResponseExpectations() { + void modifyResponseExpectations() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final Map responseExpectations = new HashMap<>(); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestClientPojoAdapter.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestClientPojoAdapter.java index 0d8957c4d5..d703a1c273 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestClientPojoAdapter.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestClientPojoAdapter.java @@ -32,9 +32,9 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestClientPojoAdapter { +class TestClientPojoAdapter { @Test - public void modifyRequest() throws Exception { + void modifyRequest() { final ClientPOJOAdapter adapter = new ClassicTestClientAdapter(); final Map request = new HashMap<>(); final Map request2 = adapter.modifyRequest(request); @@ -43,7 +43,7 @@ public void modifyRequest() throws Exception { } @Test - public void checkRequestSupport() throws Exception { + void checkRequestSupport() throws Exception { final ClientPOJOAdapter adapter = new ClassicTestClientAdapter(); final String reason = adapter.checkRequestSupport(null); @@ -53,7 +53,7 @@ public void checkRequestSupport() throws Exception { } @Test - public void checkRequestSupportThrows() throws Exception { + void checkRequestSupportThrows() { final ClientPOJOAdapter adapter = new ClientPOJOAdapter() { @Override diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestClientTestingAdapter.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestClientTestingAdapter.java index 8c134f3bc3..0af0302de7 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestClientTestingAdapter.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestClientTestingAdapter.java @@ -31,10 +31,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestClientTestingAdapter { +class TestClientTestingAdapter { @Test - public void getHttpClientPOJOAdapter() throws Exception { + void getHttpClientPOJOAdapter() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final ClientPOJOAdapter pojoAdapter = adapter.getClientPOJOAdapter(); @@ -43,7 +43,7 @@ public void getHttpClientPOJOAdapter() throws Exception { } @Test - public void isRequestSupported() throws Exception { + void isRequestSupported() { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final Map request = null; diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestFrameworkTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestFrameworkTest.java index 6d7baa9163..8a18f49e9a 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestFrameworkTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestFrameworkTest.java @@ -43,14 +43,14 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestFrameworkTest { +class TestFrameworkTest { @Test - public void defaults() throws Exception { + void defaults() throws Exception { final FrameworkTest test = new FrameworkTest(); final Map request = test.initRequest(); Assertions.assertNotNull(request, "request should not be null"); - Assertions.assertEquals(request.get(METHOD), "GET", "Default method should be GET"); + Assertions.assertEquals("GET", request.get(METHOD), "Default method should be GET"); Assertions.assertEquals(TestingFramework.DEFAULT_REQUEST_BODY, request.get(BODY), "Default request body expected."); @@ -83,7 +83,7 @@ public void defaults() throws Exception { } @Test - public void changeStatus() throws Exception { + void changeStatus() { final Map testMap = new HashMap<>(); final Map response = new HashMap<>(); testMap.put(RESPONSE, response); @@ -96,7 +96,7 @@ public void changeStatus() throws Exception { } @Test - public void changeMethod() throws Exception { + void changeMethod() throws Exception { final Map testMap = new HashMap<>(); final Map request = new HashMap<>(); testMap.put(REQUEST, request); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestTestingFramework.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestTestingFramework.java index dfb09be967..2651a1876a 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestTestingFramework.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestTestingFramework.java @@ -33,7 +33,6 @@ 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.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; @@ -46,8 +45,6 @@ import java.util.Set; import org.apache.hc.core5.http.ContentType; -import org.apache.hc.core5.http.HttpVersion; -import org.apache.hc.core5.http.ProtocolVersion; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; @@ -56,10 +53,10 @@ import org.mockito.ArgumentMatchers; import org.mockito.Mockito; -public class TestTestingFramework { +class TestTestingFramework { @Test - public void ensureDefaultMapsUnmodifiable() throws Exception { + void ensureDefaultMapsUnmodifiable() { assertUnmodifiable(TestingFramework.DEFAULT_REQUEST_QUERY); assertUnmodifiable(TestingFramework.DEFAULT_RESPONSE_HEADERS); } @@ -83,13 +80,13 @@ private TestingFramework newWebServerTestingFramework() throws TestingFrameworkE } @Test - public void runTestsWithoutSettingAdapterThrows() throws Exception { + void runTestsWithoutSettingAdapterThrows() throws Exception { final TestingFramework framework = newWebServerTestingFramework(); Assertions.assertThrows(TestingFrameworkException.class, () -> framework.runTests()); } @Test - public void nullAdapterThrows() throws Exception { + void nullAdapterThrows() throws Exception { final ClientTestingAdapter adapter = null; final TestingFramework framework = newWebServerTestingFramework(adapter); @@ -97,7 +94,7 @@ public void nullAdapterThrows() throws Exception { } @Test - public void nullSetAdapterThrows() throws Exception { + void nullSetAdapterThrows() throws Exception { final ClientTestingAdapter adapter = null; final TestingFramework framework = newWebServerTestingFramework(adapter); @@ -106,7 +103,7 @@ public void nullSetAdapterThrows() throws Exception { } @Test - public void goodAdapterWithConstructor() throws Exception { + void goodAdapterWithConstructor() throws Exception { final ClientTestingAdapter adapter = Mockito.mock(ClientTestingAdapter.class); // Have isRequestSupported() return false so no test will run. @@ -138,7 +135,7 @@ private TestingFramework newFrameworkAndSetAdapter(final ClientTestingAdapter ad } @Test - public void goodAdapterWithSetter() throws Exception { + void goodAdapterWithSetter() throws Exception { final ClientTestingAdapter adapter = Mockito.mock(ClientTestingAdapter.class); final TestingFramework framework = newFrameworkAndSetAdapter(adapter); @@ -150,7 +147,7 @@ public void goodAdapterWithSetter() throws Exception { } @Test - public void addTest() throws Exception { + void addTest() throws Exception { final TestingFrameworkRequestHandler mockRequestHandler = Mockito.mock(TestingFrameworkRequestHandler.class); final ClientTestingAdapter adapter = new ClientTestingAdapter() { @@ -213,7 +210,7 @@ public void describeTo(final Description description) { } @Test - public void statusCheck() throws Exception { + void statusCheck() throws Exception { final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( @@ -249,7 +246,7 @@ private Map alreadyCheckedResponse() { } @Test - public void responseAlreadyChecked() throws Exception { + void responseAlreadyChecked() throws Exception { final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( @@ -270,7 +267,7 @@ public Map execute( } @Test - public void bodyCheck() throws Exception { + void bodyCheck() throws Exception { final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( @@ -298,7 +295,7 @@ public Map execute( } @Test - public void responseContentTypeCheck() throws Exception { + void responseContentTypeCheck() throws Exception { final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( @@ -329,7 +326,7 @@ public Map execute( } @Test - public void deepcopy() throws Exception { + 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); @@ -351,7 +348,7 @@ public void deepcopy() throws Exception { } @Test - public void removedHeaderCheck() throws Exception { + void removedHeaderCheck() throws Exception { final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( @@ -387,7 +384,7 @@ public Map execute( } @Test - public void changedHeaderCheck() throws Exception { + void changedHeaderCheck() throws Exception { final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( @@ -432,7 +429,7 @@ private Object deepcopy(final Object obj) { } @Test - public void requestMethodUnexpected() throws Exception { + void requestMethodUnexpected() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter() { @Override public Map execute( @@ -470,7 +467,7 @@ public Map execute( } @Test - public void status201() throws Exception { + void status201() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); final TestingFramework framework = newFrameworkAndSetAdapter(adapter); @@ -486,7 +483,7 @@ public void status201() throws Exception { } @Test - public void deepcopyOfTest() throws Exception { + void deepcopyOfTest() throws Exception { final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override @@ -518,7 +515,7 @@ public Map execute( } @Test - public void removeParameter() throws Exception { + void removeParameter() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter() { @Override public Map execute( @@ -543,7 +540,7 @@ public Map execute( } @Test - public void changeParameter() throws Exception { + void changeParameter() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter() { @Override public Map execute( @@ -568,7 +565,7 @@ public Map execute( } @Test - public void removeHeader() throws Exception { + void removeHeader() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter() { @Override public Map execute( @@ -593,7 +590,7 @@ public Map execute( } @Test - public void changeHeader() throws Exception { + void changeHeader() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter() { @Override public Map execute( @@ -618,7 +615,7 @@ public Map execute( } @Test - public void changeBody() throws Exception { + void changeBody() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter() { @Override public Map execute( @@ -642,7 +639,7 @@ public Map execute( } @Test - public void changeContentType() throws Exception { + void changeContentType() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter() { @Override public Map execute( @@ -666,31 +663,7 @@ public Map execute( } @Test - public void changeProtocolVersion() 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 ProtocolVersion protocolVersion = (ProtocolVersion) request.get(PROTOCOL_VERSION); - Assertions.assertNotNull(protocolVersion); - request.put(PROTOCOL_VERSION, HttpVersion.HTTP_1_0); - return super.execute(defaultURI, request, requestHandler, responseExpectations); - } - }; - - final TestingFramework framework = newFrameworkAndSetAdapter(adapter); - - framework.addTest(); - - Assertions.assertThrows(TestingFrameworkException.class, () -> framework.runTests()); - } - - @Test - public void changeResponseExpectationsFails() throws Exception { + void changeResponseExpectationsFails() throws Exception { final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override public Map execute( @@ -718,7 +691,7 @@ public Map execute( } @Test - public void changeResponseStatus() throws Exception { + void changeResponseStatus() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter() { @Override public Map execute( @@ -749,7 +722,7 @@ public Map execute( } @Test - public void modifyRequestCalled() throws Exception { + void modifyRequestCalled() throws Exception { final TestingFrameworkRequestHandler mockRequestHandler = Mockito.mock(TestingFrameworkRequestHandler.class); final String UNLIKELY_ITEM = "something_unlikely_to_be_in_a_real_request"; @@ -791,7 +764,7 @@ public Map modifyRequest(final Map request) { } @Test - public void modifyResponseExpectationsCalled() throws Exception { + void modifyResponseExpectationsCalled() throws Exception { final TestingFrameworkRequestHandler mockRequestHandler = Mockito.mock(TestingFrameworkRequestHandler.class); final String UNLIKELY_ITEM = "something_unlikely_to_be_in_a_real_response"; @@ -835,7 +808,7 @@ public Map modifyResponseExpectations( } @Test - public void adapterDoesNotSupport() throws Exception { + void adapterDoesNotSupport() throws Exception { final ClientTestingAdapter adapter = new ClientTestingAdapter() { @Override @@ -863,7 +836,7 @@ public boolean isRequestSupported(final Map request) { } @Test - public void defaultTestsWithMockedAdapter() throws Exception { + void defaultTestsWithMockedAdapter() throws Exception { final Set calledMethodSet = new HashSet<>(); final ClientTestingAdapter adapter = new ClientTestingAdapter() { @@ -891,7 +864,7 @@ public Map execute( } @Test - public void defaultTests() throws Exception { + void defaultTests() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter(); // create the framework without deleting the default tests. @@ -903,7 +876,7 @@ public void defaultTests() throws Exception { } @Test - public void addTestNoMocks() throws TestingFrameworkException { + void addTestNoMocks() throws TestingFrameworkException { final TestingFramework framework = new TestingFramework(new ClassicTestClientTestingAdapter()); @@ -972,7 +945,7 @@ public void addTestNoMocks() throws TestingFrameworkException { } @Test - public void nulls() throws TestingFrameworkException { + void nulls() throws TestingFrameworkException { final TestingFramework framework = new TestingFramework(new ClassicTestClientTestingAdapter()); @@ -1032,7 +1005,7 @@ public void nulls() throws TestingFrameworkException { } @Test - public void parameterInPath() throws Exception { + void parameterInPath() throws Exception { final ClientTestingAdapter adapter = new ClassicTestClientTestingAdapter() { @Override public Map execute(final String defaultURI, final Map request, diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestTestingFrameworkRequestHandler.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestTestingFrameworkRequestHandler.java index 87e312f508..04c2e86a85 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestTestingFrameworkRequestHandler.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/framework/TestTestingFrameworkRequestHandler.java @@ -36,9 +36,9 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestTestingFrameworkRequestHandler { +class TestTestingFrameworkRequestHandler { @Test - public void assertNothingThrown() throws Exception { + void assertNothingThrown() throws Exception { final TestingFrameworkRequestHandler handler = new TestingFrameworkRequestHandler() { @Override @@ -51,7 +51,7 @@ public void handle(final ClassicHttpRequest request, final ClassicHttpResponse r } @Test - public void assertNothingThrownThrows() throws Exception { + void assertNothingThrownThrows() throws Exception { final String errorMessage = "thrown intentionally"; final TestingFrameworkRequestHandler handler = new TestingFrameworkRequestHandler() { diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/AsyncServerBootstrapFilterTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/AsyncServerBootstrapFilterTest.java index f43161c421..ebdee6816b 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/AsyncServerBootstrapFilterTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/AsyncServerBootstrapFilterTest.java @@ -33,6 +33,7 @@ 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; @@ -45,24 +46,25 @@ 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.http.protocol.UriPatternMatcher; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; -import org.apache.hc.core5.testing.nio.extension.HttpAsyncRequesterResource; -import org.apache.hc.core5.testing.nio.extension.HttpAsyncServerResource; +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; -public abstract class AsyncServerBootstrapFilterTest { +abstract class AsyncServerBootstrapFilterTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @@ -72,8 +74,10 @@ public abstract class AsyncServerBootstrapFilterTest { IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) - .setLookupRegistry(new UriPatternMatcher<>()) - .register("*", () -> new EchoHandler(2048)) + .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() { @@ -101,14 +105,14 @@ public void pushPromise( }))); @RegisterExtension - private final HttpAsyncRequesterResource clientResource = new HttpAsyncRequesterResource((bootstrap) -> bootstrap + private final HttpAsyncRequesterResource clientResource = new HttpAsyncRequesterResource(bootstrap -> bootstrap .setIOReactorConfig(IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build())); @Test - public void testFilters() throws Exception { + void testFilters() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(0), URIScheme.HTTP); final ListenerEndpoint listener = future.get(); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2AlpnTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2AlpnTest.java index ba08f8645e..0036959655 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2AlpnTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2AlpnTest.java @@ -36,6 +36,7 @@ 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; @@ -44,6 +45,8 @@ 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; @@ -57,14 +60,14 @@ 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.nio.extension.H2AsyncServerResource; -import org.apache.hc.core5.testing.nio.extension.H2MultiplexingRequesterResource; +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; -public abstract class H2AlpnTest { +abstract class H2AlpnTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @@ -90,7 +93,10 @@ public H2AlpnTest(final boolean strictALPN, final boolean h2Allowed) throws Exce .setSoTimeout(TIMEOUT) .build()) .setTlsStrategy(serverTlsStrategy) - .register("*", () -> new EchoHandler(2048)) + .setRequestRouter(RequestRouter.>builder() + .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) + .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) + .build()) ); final TlsStrategy clientTlsStrategy = new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext()); @@ -105,7 +111,7 @@ public H2AlpnTest(final boolean strictALPN, final boolean h2Allowed) throws Exce } @Test - public void testALPN() throws Exception { + void testALPN() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS); final ListenerEndpoint listener = future.get(); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2AlpnTests.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2AlpnTests.java index c1c8914366..22e8f9e96b 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2AlpnTests.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2AlpnTests.java @@ -30,11 +30,11 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; -public class H2AlpnTests { +class H2AlpnTests { @Nested @DisplayName("Strict h2 ALPN, h2 allowed") - public class StrictH2AlpnH2Allowed extends H2AlpnTest { + class StrictH2AlpnH2Allowed extends H2AlpnTest { public StrictH2AlpnH2Allowed() throws Exception { super(true, true); @@ -44,9 +44,9 @@ public StrictH2AlpnH2Allowed() throws Exception { @Nested @DisplayName("Strict h2 ALPN, h2 disallowed") - public class StrictH2AlpnH2Disllowed extends H2AlpnTest { + class StrictH2AlpnH2Disallowed extends H2AlpnTest { - public StrictH2AlpnH2Disllowed() throws Exception { + public StrictH2AlpnH2Disallowed() throws Exception { super(true, false); } @@ -54,9 +54,9 @@ public StrictH2AlpnH2Disllowed() throws Exception { @Nested @DisplayName("Non-strict h2 ALPN, h2 allowed") - public class NonStrictH2AlpnH2Disllowed extends H2AlpnTest { + class NonStrictH2AlpnH2Disallowed extends H2AlpnTest { - public NonStrictH2AlpnH2Disllowed() throws Exception { + public NonStrictH2AlpnH2Disallowed() throws Exception { super(false, true); } diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ConnPoolTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ConnPoolTest.java index 1d78315e69..cf80289d34 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ConnPoolTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ConnPoolTest.java @@ -28,15 +28,16 @@ package org.apache.hc.core5.testing.nio; import java.net.InetSocketAddress; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicLong; -import org.apache.hc.core5.concurrent.FutureCallback; +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; @@ -46,15 +47,15 @@ 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.nio.extension.H2AsyncServerResource; -import org.apache.hc.core5.testing.nio.extension.H2MultiplexingRequesterResource; +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; -public class H2ConnPoolTest { +class H2ConnPoolTest { private static final Timeout TIMEOUT = Timeout.ofSeconds(30); @@ -71,7 +72,10 @@ public H2ConnPoolTest() throws Exception { IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) - .register("*", () -> new EchoHandler(2048)) + .setRequestRouter(RequestRouter.>builder() + .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) + .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) + .build()) ); this.clientConnCount = new AtomicLong(); @@ -92,12 +96,12 @@ public void connected(final IOSession session) { } @BeforeEach - public void resetCounts() { + void resetCounts() { clientConnCount.set(0); } @Test - public void testManyGetSession() throws Exception { + void testManyGetSession() throws Exception { final int n = 200; final HttpAsyncServer server = serverResource.start(); @@ -108,29 +112,18 @@ public void testManyGetSession() throws Exception { final H2MultiplexingRequester requester = clientResource.start(); final H2ConnPool connPool = requester.getConnPool(); - final CountDownLatch latch = new CountDownLatch(n); + 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, new FutureCallback() { - - @Override - public void completed(final IOSession session) { - session.enqueue(new PingCommand(new BasicPingHandler( - result -> { - latch.countDown(); - })), Command.Priority.IMMEDIATE); - } - - @Override - public void failed(final Exception ex) { - latch.countDown(); - } - - @Override - public void cancelled() { - latch.countDown(); - } - - }); + connPool.getSession(target, TIMEOUT, latch); } Assertions.assertTrue(latch.await(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit())); @@ -141,34 +134,17 @@ public void cancelled() { } @Test - public void testManyGetSessionFailures() throws Exception { + 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 CountDownLatch latch = new CountDownLatch(n); - final ConcurrentLinkedQueue concurrentConnections = new ConcurrentLinkedQueue<>(); - for (int i = 0; i < n; i++) { - connPool.getSession(target, TIMEOUT, new FutureCallback() { + final CountDownLatchFutureCallback latch = new CountDownLatchFutureCallback<>(n); - @Override - public void completed(final IOSession session) { - latch.countDown(); - } - - @Override - public void failed(final Exception ex) { - latch.countDown(); - } - - @Override - public void cancelled() { - latch.countDown(); - } - - }); + for (int i = 0; i < n; i++) { + connPool.getSession(target, TIMEOUT, latch); } requester.initiateShutdown(); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2CoreTransportMultiplexingTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2CoreTransportMultiplexingTest.java index b19abba1be..28b14c5483 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2CoreTransportMultiplexingTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2CoreTransportMultiplexingTest.java @@ -33,11 +33,11 @@ import java.util.LinkedList; import java.util.Queue; import java.util.Random; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import org.apache.hc.core5.concurrent.Cancellable; -import org.apache.hc.core5.concurrent.FutureCallback; +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; @@ -46,26 +46,27 @@ 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.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.http.protocol.UriPatternMatcher; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.impl.nio.bootstrap.H2MultiplexingRequester; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; -import org.apache.hc.core5.testing.nio.extension.H2AsyncServerResource; -import org.apache.hc.core5.testing.nio.extension.H2MultiplexingRequesterResource; +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; -public abstract class H2CoreTransportMultiplexingTest { +abstract class H2CoreTransportMultiplexingTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @@ -83,8 +84,10 @@ public H2CoreTransportMultiplexingTest(final URIScheme scheme) { IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) - .setLookupRegistry(new UriPatternMatcher<>()) - .register("*", () -> new EchoHandler(2048)) + .setRequestRouter(RequestRouter.>builder() + .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) + .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) + .build()) ); this.clientResource = new H2MultiplexingRequesterResource(bootstrap -> bootstrap .setIOReactorConfig(IOReactorConfig.custom() @@ -94,7 +97,7 @@ public H2CoreTransportMultiplexingTest(final URIScheme scheme) { } @Test - public void testSequentialRequests() throws Exception { + void testSequentialRequests() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(0), scheme); final ListenerEndpoint listener = future.get(); @@ -137,7 +140,7 @@ public void testSequentialRequests() throws Exception { } @Test - public void testMultiplexedRequests() throws Exception { + void testMultiplexedRequests() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(0), scheme); final ListenerEndpoint listener = future.get(); @@ -172,7 +175,7 @@ public void testMultiplexedRequests() throws Exception { } @Test - public void testValidityCheck() throws Exception { + void testValidityCheck() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(0), scheme); final ListenerEndpoint listener = future.get(); @@ -220,7 +223,7 @@ public void testValidityCheck() throws Exception { } @Test - public void testMultiplexedRequestCancellation() throws Exception { + void testMultiplexedRequestCancellation() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(0), scheme); final ListenerEndpoint listener = future.get(); @@ -229,7 +232,7 @@ public void testMultiplexedRequestCancellation() throws Exception { final int reqNo = 20; - final CountDownLatch countDownLatch = new CountDownLatch(reqNo); + 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++) { @@ -237,24 +240,7 @@ public void testMultiplexedRequestCancellation() throws Exception { new BasicClientExchangeHandler<>(new BasicRequestProducer(Method.POST, target, "/stuff", new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), - new FutureCallback>() { - - @Override - public void completed(final Message result) { - countDownLatch.countDown(); - } - - @Override - public void failed(final Exception ex) { - countDownLatch.countDown(); - } - - @Override - public void cancelled() { - countDownLatch.countDown(); - } - - }), + countDownLatch), TIMEOUT, HttpCoreContext.create()); Thread.sleep(random.nextInt(10)); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2CoreTransportTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2CoreTransportTest.java index d0fa4f269e..9079b9aef2 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2CoreTransportTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2CoreTransportTest.java @@ -29,18 +29,20 @@ 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.protocol.UriPatternMatcher; +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.reactor.IOReactorConfig; -import org.apache.hc.core5.testing.nio.extension.H2AsyncRequesterResource; -import org.apache.hc.core5.testing.nio.extension.H2AsyncServerResource; +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; -public abstract class H2CoreTransportTest extends HttpCoreTransportTest { +abstract class H2CoreTransportTest extends HttpCoreTransportTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @@ -57,8 +59,10 @@ public H2CoreTransportTest(final URIScheme scheme) { IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) - .setLookupRegistry(new UriPatternMatcher<>()) - .register("*", () -> new EchoHandler(2048)) + .setRequestRouter(RequestRouter.>builder() + .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) + .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) + .build()) ); this.clientResource = new H2AsyncRequesterResource(bootstrap -> bootstrap .setVersionPolicy(HttpVersionPolicy.NEGOTIATE) diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2IntegrationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2IntegrationTest.java index 135dee7465..f635786f57 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2IntegrationTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2IntegrationTest.java @@ -114,15 +114,16 @@ import org.apache.hc.core5.http2.protocol.H2RequestTargetHost; import org.apache.hc.core5.reactor.Command; import org.apache.hc.core5.reactor.IOSession; -import org.apache.hc.core5.testing.nio.extension.H2TestResources; +import org.apache.hc.core5.testing.extension.nio.H2TestResources; 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.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -public abstract class H2IntegrationTest { +abstract class H2IntegrationTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); private static final Timeout LONG_TIMEOUT = Timeout.ofMinutes(2); @@ -137,6 +138,12 @@ public H2IntegrationTest(final URIScheme scheme) { this.resources = new H2TestResources(scheme, TIMEOUT); } + @BeforeEach + void setup() { + resources.server().configure(H2Config.DEFAULT); + resources.client().configure(H2Config.DEFAULT); + } + private URI createRequestURI(final InetSocketAddress serverEndpoint, final String path) { try { return new URI(scheme.id, null, "localhost", serverEndpoint.getPort(), path, null, null); @@ -146,7 +153,7 @@ private URI createRequestURI(final InetSocketAddress serverEndpoint, final Strin } @Test - public void testSimpleGet() throws Exception { + void testSimpleGet() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); @@ -178,7 +185,7 @@ public void testSimpleGet() throws Exception { } @Test - public void testSimpleHead() throws Exception { + void testSimpleHead() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); @@ -204,7 +211,7 @@ public void testSimpleHead() throws Exception { } @Test - public void testLargeGet() throws Exception { + void testLargeGet() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); @@ -250,7 +257,7 @@ public void testLargeGet() throws Exception { } @Test - public void testBasicPost() throws Exception { + void testBasicPost() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); @@ -283,7 +290,7 @@ public void testBasicPost() throws Exception { } @Test - public void testLargePost() throws Exception { + void testLargePost() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); @@ -313,14 +320,18 @@ public void testLargePost() throws Exception { } @Test - public void testSlowResponseConsumer() throws Exception { + void testSlowResponseConsumer() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); server.register("/", () -> new MultiLineResponseHandler("0123456789abcd", 3)); final InetSocketAddress serverEndpoint = server.start(); - client.start(H2Config.custom().setInitialWindowSize(16).build()); + client.configure(H2Config.custom() + .setInitialWindowSize(16) + .build()); + client.start(); + final Future connectFuture = client.connect( "localhost", serverEndpoint.getPort(), TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); @@ -365,7 +376,7 @@ protected String consumeData( } @Test - public void testSlowRequestProducer() throws Exception { + void testSlowRequestProducer() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); @@ -414,7 +425,7 @@ protected void produceData(final ContentType contentType, final OutputStream out } @Test - public void testSlowResponseProducer() throws Exception { + void testSlowResponseProducer() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); @@ -467,9 +478,10 @@ protected void handle( }); final InetSocketAddress serverEndpoint = server.start(); - client.start(H2Config.custom() + client.configure(H2Config.custom() .setInitialWindowSize(512) .build()); + client.start(); final Future connectFuture = client.connect( "localhost", serverEndpoint.getPort(), TIMEOUT); @@ -493,11 +505,10 @@ protected void handle( } @Test - public void testPush() throws Exception { + void testPush() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); - final InetSocketAddress serverEndpoint = server.start(); server.register("/hello", () -> new MessageExchangeHandler(new DiscardingEntityConsumer<>()) { @Override @@ -506,7 +517,7 @@ protected void handle( final AsyncServerRequestHandler.ResponseTrigger responseTrigger, final HttpContext context) throws IOException, HttpException { responseTrigger.pushPromise( - new BasicHttpRequest(Method.GET, createRequestURI(serverEndpoint, "/stuff")), + new BasicHttpRequest(Method.GET, new HttpHost(scheme.id, "localhost"), "/stuff"), context, new BasicPushProducer(new MultiLineEntityProducer("Pushing lots of stuff", 500))); responseTrigger.submitResponse( @@ -514,8 +525,12 @@ protected void handle( context); } }); + final InetSocketAddress serverEndpoint = server.start(); - client.start(H2Config.custom().setPushEnabled(true).build()); + client.configure(H2Config.custom() + .setPushEnabled(true) + .build()); + client.start(); final BlockingQueue> pushMessageQueue = new LinkedBlockingDeque<>(); @@ -564,12 +579,11 @@ protected void handleResponse( } @Test - public void testPushRefused() throws Exception { + void testPushRefused() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); final BlockingQueue pushResultQueue = new LinkedBlockingDeque<>(); - final InetSocketAddress serverEndpoint = server.start(); server.register("/hello", new Supplier() { @Override @@ -583,7 +597,7 @@ protected void handle( final HttpContext context) throws IOException, HttpException { responseTrigger.pushPromise( - new BasicHttpRequest(Method.GET, createRequestURI(serverEndpoint, "/stuff")), + new BasicHttpRequest(Method.GET, new HttpHost(scheme.id, "localhost"), "/stuff"), context, new BasicPushProducer(AsyncEntityProducers.create("Pushing all sorts of stuff")) { @Override @@ -594,7 +608,7 @@ public void failed(final Exception cause) { }); responseTrigger.pushPromise( - new BasicHttpRequest(Method.GET, createRequestURI(serverEndpoint, "/more-stuff")), + new BasicHttpRequest(Method.GET, new HttpHost(scheme.id, "localhost"), "/more-stuff"), context, new BasicPushProducer(new MultiLineEntityProducer("Pushing lots of stuff", 500)) { @Override @@ -612,8 +626,12 @@ public void failed(final Exception cause) { } }); + final InetSocketAddress serverEndpoint = server.start(); - client.start(H2Config.custom().setPushEnabled(true).build()); + client.configure(H2Config.custom() + .setPushEnabled(true) + .build()); + client.start(); final Future connectFuture = client.connect( "localhost", serverEndpoint.getPort(), TIMEOUT); @@ -642,14 +660,21 @@ public void failed(final Exception cause) { } @Test - public void testExcessOfConcurrentStreams() throws Exception { + void testExcessOfConcurrentStreams() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); server.register("/", () -> new MultiLineResponseHandler("0123456789abcdef", 2000)); - final InetSocketAddress serverEndpoint = server.start(H2Config.custom().setMaxConcurrentStreams(20).build()); + server.configure(H2Config.custom() + .setMaxConcurrentStreams(20) + .build()); + final InetSocketAddress serverEndpoint = server.start(); + + client.configure(H2Config.custom() + .setMaxConcurrentStreams(20) + .build()); + client.start(); - client.start(H2Config.custom().setMaxConcurrentStreams(20).build()); final Future connectFuture = client.connect( "localhost", serverEndpoint.getPort(), TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); @@ -674,7 +699,7 @@ public void testExcessOfConcurrentStreams() throws Exception { } @Test - public void testExpectationFailed() throws Exception { + void testExpectationFailed() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); @@ -689,7 +714,7 @@ protected void handle( } }); - final InetSocketAddress serverEndpoint = server.start(null, handler -> new BasicAsyncServerExpectationDecorator(handler) { + server.configure(handler -> new BasicAsyncServerExpectationDecorator(handler) { @Override protected AsyncResponseProducer verify(final HttpRequest request, final HttpContext context) throws IOException, HttpException { @@ -700,8 +725,20 @@ protected AsyncResponseProducer verify(final HttpRequest request, final HttpCont return new BasicResponseProducer(HttpStatus.SC_UNAUTHORIZED, "You shall not pass"); } } - }, H2Config.DEFAULT); + }); + 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(); client.start(); final Future connectFuture = client.connect( "localhost", serverEndpoint.getPort(), TIMEOUT); @@ -732,7 +769,7 @@ protected AsyncResponseProducer verify(final HttpRequest request, final HttpCont } @Test - public void testPrematureResponse() throws Exception { + void testPrematureResponse() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); @@ -817,7 +854,7 @@ public void releaseResources() { } @Test - public void testMessageWithTrailers() throws Exception { + void testMessageWithTrailers() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); @@ -875,7 +912,7 @@ protected void handle( } @Test - public void testConnectionPing() throws Exception { + void testConnectionPing() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); @@ -907,7 +944,7 @@ public void testConnectionPing() throws Exception { } @Test - public void testRequestWithInvalidConnectionHeader() throws Exception { + void testRequestWithInvalidConnectionHeader() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); @@ -922,29 +959,31 @@ public void testRequestWithInvalidConnectionHeader() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, createRequestURI(serverEndpoint, "/hello")); request.addHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); - final HttpCoreContext coreContext = HttpCoreContext.create(); + final HttpCoreContext context = HttpCoreContext.create(); final Future> future = streamEndpoint.execute( new BasicRequestProducer(request, null), new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), - coreContext, null); + 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 = coreContext.getEndpointDetails(); + final EndpointDetails endpointDetails = context.getEndpointDetails(); assertThat(endpointDetails.getRequestCount(), CoreMatchers.equalTo(0L)); } } @Test - public void testHeaderTooLarge() throws Exception { + void testHeaderTooLarge() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); - final InetSocketAddress serverEndpoint = server.start(H2Config.custom() + server.configure(H2Config.custom() .setMaxHeaderListSize(100) .build()); + final InetSocketAddress serverEndpoint = server.start(); + client.start(); final Future connectFuture = client.connect( @@ -966,17 +1005,18 @@ public void testHeaderTooLarge() throws Exception { } @Test - public void testHeaderTooLargePost() throws Exception { + void testHeaderTooLargePost() throws Exception { final H2TestServer server = resources.server(); final H2TestClient client = resources.client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); - final InetSocketAddress serverEndpoint = server.start(H2Config.custom() + server.configure(H2Config.custom() .setMaxHeaderListSize(100) .build()); - client.start( - new DefaultHttpProcessor(H2RequestContent.INSTANCE, H2RequestTargetHost.INSTANCE, H2RequestConnControl.INSTANCE), - H2Config.DEFAULT); + final InetSocketAddress serverEndpoint = server.start(); + client.configure( + new DefaultHttpProcessor(H2RequestContent.INSTANCE, H2RequestTargetHost.INSTANCE, H2RequestConnControl.INSTANCE)); + client.start(); final Future connectFuture = client.connect( "localhost", serverEndpoint.getPort(), TIMEOUT); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ProtocolNegotiationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ProtocolNegotiationTest.java index 4fdcdfaeb9..8b2b1d2741 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ProtocolNegotiationTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ProtocolNegotiationTest.java @@ -32,6 +32,7 @@ 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.HttpHost; import org.apache.hc.core5.http.HttpResponse; @@ -42,7 +43,9 @@ 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.support.BasicRequestProducer; @@ -50,14 +53,14 @@ import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; -import org.apache.hc.core5.testing.nio.extension.H2AsyncRequesterResource; -import org.apache.hc.core5.testing.nio.extension.H2AsyncServerResource; +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; -public class H2ProtocolNegotiationTest { +class H2ProtocolNegotiationTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @@ -73,7 +76,10 @@ public H2ProtocolNegotiationTest() { IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) - .register("*", () -> new EchoHandler(2048)) + .setRequestRouter(RequestRouter.>builder() + .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) + .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) + .build()) ); this.clientResource = new H2AsyncRequesterResource(bootstrap -> bootstrap .setVersionPolicy(HttpVersionPolicy.NEGOTIATE) @@ -84,7 +90,7 @@ public H2ProtocolNegotiationTest() { } @Test - public void testForceHttp1() throws Exception { + void testForceHttp1() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS); final ListenerEndpoint listener = future.get(); @@ -107,7 +113,7 @@ public void testForceHttp1() throws Exception { } @Test - public void testForceHttp2() throws Exception { + void testForceHttp2() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS); final ListenerEndpoint listener = future.get(); @@ -130,7 +136,7 @@ public void testForceHttp2() throws Exception { } @Test - public void testNegotiateProtocol() throws Exception { + void testNegotiateProtocol() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS); final ListenerEndpoint listener = future.get(); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerBootstrapFiltersTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerBootstrapFiltersTest.java index a1ce86105c..7638d2013a 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerBootstrapFiltersTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ServerBootstrapFiltersTest.java @@ -33,6 +33,7 @@ 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; @@ -45,37 +46,40 @@ 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.http.protocol.UriPatternMatcher; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; -import org.apache.hc.core5.testing.nio.extension.H2AsyncRequesterResource; -import org.apache.hc.core5.testing.nio.extension.H2AsyncServerResource; +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; -public abstract class H2ServerBootstrapFiltersTest { +abstract class H2ServerBootstrapFiltersTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @RegisterExtension - private final H2AsyncServerResource serverResource = new H2AsyncServerResource((bootstrap) -> bootstrap - .setLookupRegistry(new UriPatternMatcher<>()) + private final H2AsyncServerResource serverResource = new H2AsyncServerResource(bootstrap -> bootstrap .setVersionPolicy(HttpVersionPolicy.NEGOTIATE) .setIOReactorConfig( IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) - .register("*", () -> new EchoHandler(2048)) + .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() { @@ -110,7 +114,7 @@ public void pushPromise( .build())); @Test - public void testSequentialRequests() throws Exception { + void testSequentialRequests() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(0), URIScheme.HTTP); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2SocksProxyCoreTransportTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2SocksProxyCoreTransportTest.java index 9ad75bc7e9..5c32461301 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2SocksProxyCoreTransportTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2SocksProxyCoreTransportTest.java @@ -29,20 +29,22 @@ 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.protocol.UriPatternMatcher; +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.reactor.IOReactorConfig; import org.apache.hc.core5.testing.extension.SocksProxyResource; -import org.apache.hc.core5.testing.nio.extension.H2AsyncRequesterResource; -import org.apache.hc.core5.testing.nio.extension.H2AsyncServerResource; +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.Order; import org.junit.jupiter.api.extension.RegisterExtension; -public abstract class H2SocksProxyCoreTransportTest extends HttpCoreTransportTest { +abstract class H2SocksProxyCoreTransportTest extends HttpCoreTransportTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @@ -63,8 +65,10 @@ public H2SocksProxyCoreTransportTest(final URIScheme scheme) { IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) - .setLookupRegistry(new UriPatternMatcher<>()) - .register("*", () -> new EchoHandler(2048)) + .setRequestRouter(RequestRouter.>builder() + .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) + .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) + .build()) ); this.clientResource = new H2AsyncRequesterResource(bootstrap -> bootstrap .setVersionPolicy(HttpVersionPolicy.NEGOTIATE) diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1AuthenticationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1AuthenticationTest.java index b2b499d72b..6385fc6989 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1AuthenticationTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1AuthenticationTest.java @@ -34,6 +34,7 @@ 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; @@ -48,8 +49,10 @@ 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; @@ -59,14 +62,14 @@ 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.nio.extension.HttpAsyncRequesterResource; -import org.apache.hc.core5.testing.nio.extension.HttpAsyncServerResource; +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; -public abstract class Http1AuthenticationTest { +abstract class Http1AuthenticationTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @@ -81,8 +84,10 @@ public Http1AuthenticationTest(final boolean respondImmediately) { IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) - .setLookupRegistry(null) // same as the default - .register("*", () -> new EchoHandler(2048)) + .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 @@ -123,7 +128,7 @@ protected AsyncEntityProducer generateResponseContent(final HttpResponse unautho } @Test - public void testGetRequestAuthentication() throws Exception { + void testGetRequestAuthentication() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(0), URIScheme.HTTP); final ListenerEndpoint listener = future.get(); @@ -157,7 +162,7 @@ public void testGetRequestAuthentication() throws Exception { } @Test - public void testPostRequestAuthentication() throws Exception { + void testPostRequestAuthentication() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(0), URIScheme.HTTP); final ListenerEndpoint listener = future.get(); @@ -195,7 +200,7 @@ public void testPostRequestAuthentication() throws Exception { } @Test - public void testPostRequestAuthenticationNoExpectContinue() throws Exception { + void testPostRequestAuthenticationNoExpectContinue() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(0), URIScheme.HTTP); final ListenerEndpoint listener = future.get(); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1CoreTransportTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1CoreTransportTest.java index 6476964023..cddbf326a4 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1CoreTransportTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1CoreTransportTest.java @@ -33,6 +33,7 @@ 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; @@ -47,24 +48,25 @@ 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.http.protocol.UriPatternMatcher; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.reactor.ListenerEndpoint; -import org.apache.hc.core5.testing.nio.extension.HttpAsyncRequesterResource; -import org.apache.hc.core5.testing.nio.extension.HttpAsyncServerResource; +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; -public abstract class Http1CoreTransportTest extends HttpCoreTransportTest { +abstract class Http1CoreTransportTest extends HttpCoreTransportTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @@ -80,8 +82,10 @@ public Http1CoreTransportTest(final URIScheme scheme) { IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) - .setLookupRegistry(new UriPatternMatcher<>()) - .register("*", () -> new EchoHandler(2048)) + .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() { @@ -128,7 +132,7 @@ HttpAsyncRequester clientStart() { } @Test - public void testSequentialRequestsNonPersistentConnection() throws Exception { + void testSequentialRequestsNonPersistentConnection() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(0), scheme); final ListenerEndpoint listener = future.get(); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1IntegrationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1IntegrationTest.java index 0103df00cf..9c8b80ba33 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1IntegrationTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1IntegrationTest.java @@ -56,6 +56,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.http.ConnectionReuseStrategy; import org.apache.hc.core5.http.ContentLengthStrategy; @@ -100,6 +101,7 @@ import org.apache.hc.core5.http.nio.ResponseChannel; 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.DigestingEntityConsumer; import org.apache.hc.core5.http.nio.entity.DigestingEntityProducer; import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer; @@ -125,7 +127,7 @@ 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.nio.extension.Http1TestResources; +import org.apache.hc.core5.testing.extension.nio.Http1TestResources; import org.apache.hc.core5.util.CharArrayBuffer; import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.util.Timeout; @@ -134,13 +136,15 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -public abstract class Http1IntegrationTest { +abstract class Http1IntegrationTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); private static final Timeout LONG_TIMEOUT = Timeout.ofMinutes(2); private final URIScheme scheme; + private final ReentrantLock lock = new ReentrantLock(); + @RegisterExtension private final Http1TestResources resources; @@ -158,7 +162,7 @@ private URI createRequestURI(final InetSocketAddress serverEndpoint, final Strin } @Test - public void testSimpleGet() throws Exception { + void testSimpleGet() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -185,7 +189,7 @@ public void testSimpleGet() throws Exception { } @Test - public void testSimpleGetConnectionClose() throws Exception { + void testSimpleGetConnectionClose() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -215,13 +219,13 @@ public void testSimpleGetConnectionClose() throws Exception { } @Test - public void testSimpleGetIdentityTransfer() throws Exception { + void testSimpleGetIdentityTransfer() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); - final HttpProcessor httpProcessor = new DefaultHttpProcessor(new RequestValidateHost()); - final InetSocketAddress serverEndpoint = server.start(httpProcessor, Http1Config.DEFAULT); + server.configure(new DefaultHttpProcessor(new RequestValidateHost())); + final InetSocketAddress serverEndpoint = server.start(); client.start(); @@ -249,13 +253,13 @@ public void testSimpleGetIdentityTransfer() throws Exception { } @Test - public void testPostIdentityTransfer() throws Exception { + void testPostIdentityTransfer() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); - final HttpProcessor httpProcessor = new DefaultHttpProcessor(new RequestValidateHost()); - final InetSocketAddress serverEndpoint = server.start(httpProcessor, Http1Config.DEFAULT); + server.configure(new DefaultHttpProcessor(new RequestValidateHost())); + final InetSocketAddress serverEndpoint = server.start(); client.start(); @@ -284,13 +288,13 @@ public void testPostIdentityTransfer() throws Exception { } @Test - public void testPostIdentityTransferOutOfSequenceResponse() throws Exception { + void testPostIdentityTransferOutOfSequenceResponse() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); server.register("/hello", () -> new ImmediateResponseExchangeHandler(500, "Go away")); - final HttpProcessor httpProcessor = new DefaultHttpProcessor(new RequestValidateHost()); - final InetSocketAddress serverEndpoint = server.start(httpProcessor, Http1Config.DEFAULT); + server.configure(new DefaultHttpProcessor(new RequestValidateHost())); + final InetSocketAddress serverEndpoint = server.start(); client.start(); @@ -319,7 +323,7 @@ public void testPostIdentityTransferOutOfSequenceResponse() throws Exception { } @Test - public void testSimpleGetsPipelined() throws Exception { + void testSimpleGetsPipelined() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -350,7 +354,7 @@ public void testSimpleGetsPipelined() throws Exception { } @Test - public void testLargeGet() throws Exception { + void testLargeGet() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -396,7 +400,7 @@ public void testLargeGet() throws Exception { } @Test - public void testLargeGetsPipelined() throws Exception { + void testLargeGetsPipelined() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -431,7 +435,7 @@ public void testLargeGetsPipelined() throws Exception { } @Test - public void testBasicPost() throws Exception { + void testBasicPost() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -459,7 +463,7 @@ public void testBasicPost() throws Exception { } @Test - public void testBasicPostPipelined() throws Exception { + void testBasicPostPipelined() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -491,7 +495,7 @@ public void testBasicPostPipelined() throws Exception { } @Test - public void testHttp10Post() throws Exception { + void testHttp10Post() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -520,7 +524,29 @@ public void testHttp10Post() throws Exception { } @Test - public void testNoEntityPost() throws Exception { + 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(); + + client.start(); + final Future connectFuture = client.connect( + "localhost", serverEndpoint.getPort(), TIMEOUT); + final ClientSessionEndpoint streamEndpoint = connectFuture.get(); + + final HttpRequest request = new BasicHttpRequest(Method.POST, createRequestURI(serverEndpoint, "/hello")); + 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 testNoEntityPost() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -548,7 +574,7 @@ public void testNoEntityPost() throws Exception { } @Test - public void testLargePost() throws Exception { + void testLargePost() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -580,7 +606,7 @@ public void testLargePost() throws Exception { } @Test - public void testPostsPipelinedLargeResponse() throws Exception { + void testPostsPipelinedLargeResponse() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -617,7 +643,7 @@ public void testPostsPipelinedLargeResponse() throws Exception { @Test - public void testLargePostsPipelined() throws Exception { + void testLargePostsPipelined() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -653,7 +679,7 @@ public void testLargePostsPipelined() throws Exception { } @Test - public void testSimpleHead() throws Exception { + void testSimpleHead() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -679,7 +705,7 @@ public void testSimpleHead() throws Exception { } @Test - public void testSimpleHeadConnectionClose() throws Exception { + void testSimpleHeadConnectionClose() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -708,7 +734,7 @@ public void testSimpleHeadConnectionClose() throws Exception { } @Test - public void testHeadPipelined() throws Exception { + void testHeadPipelined() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -738,7 +764,7 @@ public void testHeadPipelined() throws Exception { } @Test - public void testExpectationFailed() throws Exception { + void testExpectationFailed() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -753,7 +779,7 @@ protected void handle( } }); - final InetSocketAddress serverEndpoint = server.start(null, handler -> new BasicAsyncServerExpectationDecorator(handler) { + server.configure(handler -> new BasicAsyncServerExpectationDecorator(handler) { @Override protected AsyncResponseProducer verify(final HttpRequest request, final HttpContext context) throws IOException, HttpException { @@ -764,7 +790,8 @@ protected AsyncResponseProducer verify(final HttpRequest request, final HttpCont return new BasicResponseProducer(HttpStatus.SC_UNAUTHORIZED, "You shall not pass"); } } - }, Http1Config.DEFAULT); + }); + final InetSocketAddress serverEndpoint = server.start(); client.start(); final Future sessionFuture = client.requestSession( @@ -829,7 +856,7 @@ protected AsyncResponseProducer verify(final HttpRequest request, final HttpCont } @Test - public void testExpectationFailedCloseConnection() throws Exception { + void testExpectationFailedCloseConnection() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -844,7 +871,7 @@ protected void handle( } }); - final InetSocketAddress serverEndpoint = server.start(null, handler -> new BasicAsyncServerExpectationDecorator(handler) { + server.configure(handler -> new BasicAsyncServerExpectationDecorator(handler) { @Override protected AsyncResponseProducer verify(final HttpRequest request, final HttpContext context) throws IOException, HttpException { @@ -857,7 +884,8 @@ protected AsyncResponseProducer verify(final HttpRequest request, final HttpCont return new BasicResponseProducer(response, "You shall not pass"); } } - }, Http1Config.DEFAULT); + }); + final InetSocketAddress serverEndpoint = server.start(); client.start(); final Future sessionFuture = client.requestSession( @@ -884,7 +912,7 @@ protected AsyncResponseProducer verify(final HttpRequest request, final HttpCont } @Test - public void testDelayedExpectationVerification() throws Exception { + void testDelayedExpectationVerification() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -910,8 +938,11 @@ public void handleRequest( responseChannel.sendInformation(new BasicHttpResponse(HttpStatus.SC_CONTINUE), context); } final HttpResponse response = new BasicHttpResponse(200); - synchronized (entityProducer) { + lock.lock(); + try { responseChannel.sendResponse(response, entityProducer, context); + } finally { + lock.unlock(); } } } catch (final Exception ignore) { @@ -936,15 +967,21 @@ public void streamEnd(final List trailers) throws HttpExceptio @Override public int available() { - synchronized (entityProducer) { + lock.lock(); + try { return entityProducer.available(); + } finally { + lock.unlock(); } } @Override public void produce(final DataStreamChannel channel) throws IOException { - synchronized (entityProducer) { + lock.lock(); + try { entityProducer.produce(channel); + } finally { + lock.unlock(); } } @@ -959,7 +996,11 @@ public void releaseResources() { }); final InetSocketAddress serverEndpoint = server.start(); - client.start(Http1Config.custom().setWaitForContinueTimeout(Timeout.ofMilliseconds(100)).build()); + client.configure(Http1Config.custom() + .setWaitForContinueTimeout(Timeout.ofMilliseconds(100)) + .build()); + client.start(); + final Future connectFuture = client.connect( "localhost", serverEndpoint.getPort(), TIMEOUT); final ClientSessionEndpoint streamEndpoint = connectFuture.get(); @@ -983,7 +1024,7 @@ public void releaseResources() { } @Test - public void testPrematureResponse() throws Exception { + void testPrematureResponse() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -1078,14 +1119,17 @@ public void releaseResources() { } @Test - public void testSlowResponseConsumer() throws Exception { + void testSlowResponseConsumer() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); server.register("/", () -> new MultiLineResponseHandler("0123456789abcd", 100)); final InetSocketAddress serverEndpoint = server.start(); - client.start(Http1Config.custom().setBufferSize(256).build()); + client.configure(Http1Config.custom() + .setBufferSize(256) + .build()); + client.start(); final Future connectFuture = client.connect( "localhost", serverEndpoint.getPort(), TIMEOUT); @@ -1132,7 +1176,7 @@ protected String consumeData( } @Test - public void testSlowRequestProducer() throws Exception { + void testSlowRequestProducer() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -1181,7 +1225,7 @@ protected void produceData(final ContentType contentType, final OutputStream out } @Test - public void testSlowResponseProducer() throws Exception { + void testSlowResponseProducer() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -1234,7 +1278,10 @@ protected void handle( }); final InetSocketAddress serverEndpoint = server.start(); - client.start(Http1Config.custom().setBufferSize(256).build()); + client.configure(Http1Config.custom() + .setBufferSize(256) + .build()); + client.start(); final Future connectFuture = client.connect( "localhost", serverEndpoint.getPort(), TIMEOUT); @@ -1258,7 +1305,7 @@ protected void handle( } @Test - public void testPipelinedConnectionClose() throws Exception { + void testPipelinedConnectionClose() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -1319,7 +1366,7 @@ public void testPipelinedConnectionClose() throws Exception { } @Test - public void testPipelinedInvalidRequest() throws Exception { + void testPipelinedInvalidRequest() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -1416,7 +1463,7 @@ public int write(final ByteBuffer src) throws IOException { } @Test - public void testTruncatedChunk() throws Exception { + void testTruncatedChunk() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -1501,7 +1548,7 @@ public void releaseResources() { } @Test - public void testExceptionInHandler() throws Exception { + void testExceptionInHandler() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -1535,10 +1582,11 @@ protected void handle( } @Test - public void testNoServiceHandler() throws Exception { + void testNoServiceHandler() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); + server.register("/ehh", () -> new SingleLineResponseHandler("Hi there")); final InetSocketAddress serverEndpoint = server.start(); client.start(); @@ -1559,7 +1607,7 @@ public void testNoServiceHandler() throws Exception { } @Test - public void testResponseNoContent() throws Exception { + void testResponseNoContent() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -1593,46 +1641,7 @@ protected void handle( } @Test - public void testAbsentHostHeader() throws Exception { - final Http1TestServer server = resources.server(); - final Http1TestClient client = resources.client(); - - server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); - final InetSocketAddress serverEndpoint = server.start(); - - client.start(new DefaultHttpProcessor(RequestContent.INSTANCE, RequestConnControl.INSTANCE), Http1Config.DEFAULT); - - final Future connectFuture = client.connect( - "localhost", serverEndpoint.getPort(), TIMEOUT); - final ClientSessionEndpoint streamEndpoint = connectFuture.get(); - - final HttpRequest request1 = new BasicHttpRequest(Method.GET, createRequestURI(serverEndpoint, "/hello")); - request1.setVersion(HttpVersion.HTTP_1_0); - final Future> future1 = streamEndpoint.execute( - new BasicRequestProducer(request1, null), - 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("Hi there", result1.getBody()); - - final HttpRequest request2 = new BasicHttpRequest(Method.GET, createRequestURI(serverEndpoint, "/hello")); - request2.setVersion(HttpVersion.HTTP_1_1); - final Future> future2 = streamEndpoint.execute( - new BasicRequestProducer(request2, null), - 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(400, response2.getCode()); - Assertions.assertEquals("Host header is absent", result2.getBody()); - } - - @Test - public void testMessageWithTrailers() throws Exception { + void testMessageWithTrailers() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -1690,7 +1699,7 @@ protected void handle( } @Test - public void testProtocolException() throws Exception { + void testProtocolException() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); @@ -1766,14 +1775,15 @@ public void failed(final Exception cause) { } @Test - public void testHeaderTooLarge() throws Exception { + void testHeaderTooLarge() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); - final InetSocketAddress serverEndpoint = server.start(null, Http1Config.custom() + server.configure(Http1Config.custom() .setMaxLineLength(100) .build()); + final InetSocketAddress serverEndpoint = server.start(); client.start(); final Future connectFuture = client.connect( @@ -1795,16 +1805,18 @@ public void testHeaderTooLarge() throws Exception { } @Test - public void testHeaderTooLargePost() throws Exception { + void testHeaderTooLargePost() throws Exception { final Http1TestServer server = resources.server(); final Http1TestClient client = resources.client(); server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); - final InetSocketAddress serverEndpoint = server.start(null, Http1Config.custom() + server.configure(Http1Config.custom() .setMaxLineLength(100) .build()); - client.start( - new DefaultHttpProcessor(RequestContent.INSTANCE, RequestTargetHost.INSTANCE, RequestConnControl.INSTANCE), null); + final InetSocketAddress serverEndpoint = server.start(); + client.configure( + new DefaultHttpProcessor(RequestContent.INSTANCE, RequestTargetHost.INSTANCE, RequestConnControl.INSTANCE)); + client.start(); final Future connectFuture = client.connect( "localhost", serverEndpoint.getPort(), TIMEOUT); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1SocksProxyCoreTransportTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1SocksProxyCoreTransportTest.java index 4852dbfb1d..95c06534f9 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1SocksProxyCoreTransportTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1SocksProxyCoreTransportTest.java @@ -29,6 +29,7 @@ import java.io.IOException; +import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; @@ -38,19 +39,20 @@ 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.protocol.UriPatternMatcher; +import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.testing.extension.SocksProxyResource; -import org.apache.hc.core5.testing.nio.extension.HttpAsyncRequesterResource; -import org.apache.hc.core5.testing.nio.extension.HttpAsyncServerResource; +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.junit.jupiter.api.Order; import org.junit.jupiter.api.extension.RegisterExtension; -public abstract class Http1SocksProxyCoreTransportTest extends HttpCoreTransportTest { +abstract class Http1SocksProxyCoreTransportTest extends HttpCoreTransportTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @@ -70,8 +72,10 @@ public Http1SocksProxyCoreTransportTest(final URIScheme scheme) { IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) - .setLookupRegistry(new UriPatternMatcher<>()) - .register("*", () -> new EchoHandler(2048)) + .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() { diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/HttpCoreTransportTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/HttpCoreTransportTest.java index 594b991b26..8766d950bc 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/HttpCoreTransportTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/HttpCoreTransportTest.java @@ -56,7 +56,7 @@ import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; -public abstract class HttpCoreTransportTest { +abstract class HttpCoreTransportTest { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @@ -71,7 +71,7 @@ public abstract class HttpCoreTransportTest { abstract HttpAsyncRequester clientStart() throws IOException; @Test - public void testSequentialRequests() throws Exception { + void testSequentialRequests() throws Exception { final HttpAsyncServer server = serverStart(); final Future future = server.listen(new InetSocketAddress(0), scheme); final ListenerEndpoint listener = future.get(); @@ -114,7 +114,7 @@ public void testSequentialRequests() throws Exception { } @Test - public void testSequentialRequestsNonPersistentConnection() throws Exception { + void testSequentialRequestsNonPersistentConnection() throws Exception { final HttpAsyncServer server = serverStart(); final Future future = server.listen(new InetSocketAddress(0), scheme); final ListenerEndpoint listener = future.get(); @@ -157,7 +157,7 @@ public void testSequentialRequestsNonPersistentConnection() throws Exception { } @Test - public void testSequentialRequestsSameEndpoint() throws Exception { + void testSequentialRequestsSameEndpoint() throws Exception { final HttpAsyncServer server = serverStart(); final Future future = server.listen(new InetSocketAddress(0), scheme); final ListenerEndpoint listener = future.get(); @@ -208,7 +208,7 @@ public void testSequentialRequestsSameEndpoint() throws Exception { } @Test - public void testPipelinedRequests() throws Exception { + void testPipelinedRequests() throws Exception { final HttpAsyncServer server = serverStart(); final Future future = server.listen(new InetSocketAddress(0), scheme); final ListenerEndpoint listener = future.get(); @@ -251,7 +251,7 @@ public void testPipelinedRequests() throws Exception { } @Test - public void testNonPersistentHeads() throws Exception { + void testNonPersistentHeads() throws Exception { final HttpAsyncServer server = serverStart(); final Future future = server.listen(new InetSocketAddress(0), scheme); final ListenerEndpoint listener = future.get(); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/HttpIntegrationTests.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/HttpIntegrationTests.java index 0132125e8f..2801c0cf38 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/HttpIntegrationTests.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/HttpIntegrationTests.java @@ -31,11 +31,11 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; -public class HttpIntegrationTests { +class HttpIntegrationTests { @Nested @DisplayName("Core transport (HTTP/1.1)") - public class CoreTransport extends Http1CoreTransportTest { + class CoreTransport extends Http1CoreTransportTest { public CoreTransport() { super(URIScheme.HTTP); @@ -45,7 +45,7 @@ public CoreTransport() { @Nested @DisplayName("Core transport (HTTP/1.1, TLS)") - public class CoreTransportTls extends Http1CoreTransportTest { + class CoreTransportTls extends Http1CoreTransportTest { public CoreTransportTls() { super(URIScheme.HTTPS); @@ -55,7 +55,7 @@ public CoreTransportTls() { @Nested @DisplayName("Core transport (H2)") - public class CoreTransportH2 extends H2CoreTransportTest { + class CoreTransportH2 extends H2CoreTransportTest { public CoreTransportH2() { super(URIScheme.HTTP); @@ -65,7 +65,7 @@ public CoreTransportH2() { @Nested @DisplayName("Core transport (H2, TLS)") - public class CoreTransportH2Tls extends H2CoreTransportTest { + class CoreTransportH2Tls extends H2CoreTransportTest { public CoreTransportH2Tls() { super(URIScheme.HTTPS); @@ -75,7 +75,7 @@ public CoreTransportH2Tls() { @Nested @DisplayName("Core transport (H2, multiplexing)") - public class CoreTransportH2Multiplexing extends H2CoreTransportMultiplexingTest { + class CoreTransportH2Multiplexing extends H2CoreTransportMultiplexingTest { public CoreTransportH2Multiplexing() { super(URIScheme.HTTP); @@ -85,7 +85,7 @@ public CoreTransportH2Multiplexing() { @Nested @DisplayName("Core transport (H2, multiplexing, TLS)") - public class CoreTransportH2MultiplexingTls extends H2CoreTransportMultiplexingTest { + class CoreTransportH2MultiplexingTls extends H2CoreTransportMultiplexingTest { public CoreTransportH2MultiplexingTls() { super(URIScheme.HTTPS); @@ -95,7 +95,7 @@ public CoreTransportH2MultiplexingTls() { @Nested @DisplayName("Server filters") - public class HttpFilters extends AsyncServerBootstrapFilterTest { + class HttpFilters extends AsyncServerBootstrapFilterTest { public HttpFilters() { super(); @@ -105,7 +105,7 @@ public HttpFilters() { @Nested @DisplayName("H2 Server filters") - public class H2Filters extends H2ServerBootstrapFiltersTest { + class H2Filters extends H2ServerBootstrapFiltersTest { public H2Filters() { super(); @@ -115,7 +115,7 @@ public H2Filters() { @Nested @DisplayName("Authentication") - public class Authentication extends Http1AuthenticationTest { + class Authentication extends Http1AuthenticationTest { public Authentication() { super(false); @@ -125,7 +125,7 @@ public Authentication() { @Nested @DisplayName("Authentication (immediate response)") - public class AuthenticationImmediateResponse extends Http1AuthenticationTest { + class AuthenticationImmediateResponse extends Http1AuthenticationTest { public AuthenticationImmediateResponse() { super(true); @@ -135,7 +135,7 @@ public AuthenticationImmediateResponse() { @Nested @DisplayName("Core transport (HTTP/1.1, SOCKS)") - public class CoreTransportSocksProxy extends Http1SocksProxyCoreTransportTest { + class CoreTransportSocksProxy extends Http1SocksProxyCoreTransportTest { public CoreTransportSocksProxy() { super(URIScheme.HTTP); @@ -145,7 +145,7 @@ public CoreTransportSocksProxy() { @Nested @DisplayName("Core transport (HTTP/1.1, TLS, SOCKS)") - public class CoreTransportSocksProxyTls extends Http1SocksProxyCoreTransportTest { + class CoreTransportSocksProxyTls extends Http1SocksProxyCoreTransportTest { public CoreTransportSocksProxyTls() { super(URIScheme.HTTPS); @@ -155,7 +155,7 @@ public CoreTransportSocksProxyTls() { @Nested @DisplayName("Core transport (H2, SOCKS)") - public class CoreTransportH2SocksProxy extends H2SocksProxyCoreTransportTest { + class CoreTransportH2SocksProxy extends H2SocksProxyCoreTransportTest { public CoreTransportH2SocksProxy() { super(URIScheme.HTTP); @@ -165,7 +165,7 @@ public CoreTransportH2SocksProxy() { @Nested @DisplayName("Core transport (H2, TLS, SOCKS)") - public class CoreTransportH2SocksProxyTls extends H2SocksProxyCoreTransportTest { + class CoreTransportH2SocksProxyTls extends H2SocksProxyCoreTransportTest { public CoreTransportH2SocksProxyTls() { super(URIScheme.HTTPS); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/HttpProtocolTests.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/HttpProtocolTests.java index 7f4a481ba6..d758bfc8bb 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/HttpProtocolTests.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/HttpProtocolTests.java @@ -31,11 +31,11 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; -public class HttpProtocolTests { +class HttpProtocolTests { @Nested @DisplayName("HTTP/1.1 (plain)") - public class Http1 extends Http1IntegrationTest { + class Http1 extends Http1IntegrationTest { public Http1() { super(URIScheme.HTTP); @@ -45,7 +45,7 @@ public Http1() { @Nested @DisplayName("HTTP/1.1 (TLS)") - public class Http1Tls extends Http1IntegrationTest { + class Http1Tls extends Http1IntegrationTest { public Http1Tls() { super(URIScheme.HTTPS); @@ -55,7 +55,7 @@ public Http1Tls() { @Nested @DisplayName("HTTP/2 (plain)") - public class H2 extends H2IntegrationTest { + class H2 extends H2IntegrationTest { public H2() { super(URIScheme.HTTP); @@ -65,7 +65,7 @@ public H2() { @Nested @DisplayName("HTTP/2 (TLS)") - public class H2Tls extends H2IntegrationTest { + class H2Tls extends H2IntegrationTest { public H2Tls() { super(URIScheme.HTTPS); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/JSSEProviderIntegrationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/JSSEProviderIntegrationTest.java index 61a6903305..e82dd09603 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/JSSEProviderIntegrationTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/JSSEProviderIntegrationTest.java @@ -34,19 +34,20 @@ import java.security.Provider; import java.security.SecureRandom; import java.security.Security; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.Future; import org.apache.hc.core5.http.HttpHeaders; 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.config.Http1Config; 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.BasicRequestProducer; import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http.protocol.DefaultHttpProcessor; -import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http.protocol.RequestValidateHost; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.ssl.SSLContextBuilder; @@ -54,6 +55,7 @@ import org.apache.hc.core5.util.Timeout; import org.conscrypt.Conscrypt; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.AfterEachCallback; @@ -61,18 +63,7 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.RegisterExtension; - -// @Parameterized.Parameters(name = "{0} {1}") -// public static Collection protocols() { -// return Arrays.asList(new Object[][]{ -// {"Oracle", null}, -// {"Conscrypt", "TLSv1.2"}, -// {"Conscrypt", "TLSv1.3"}, -// }); -// } - - -public abstract class JSSEProviderIntegrationTest { +abstract class JSSEProviderIntegrationTest { private final String securityProviderName; private final String protocolVersion; @@ -93,6 +84,9 @@ class SecurityProviderResource implements BeforeEachCallback, AfterEachCallback @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) { @@ -210,7 +204,7 @@ private URI createRequestURI(final InetSocketAddress serverEndpoint, final Strin } @Test - public void testSimpleGet() throws Exception { + void testSimpleGet() throws Exception { server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); final InetSocketAddress serverEndpoint = server.start(); @@ -234,7 +228,7 @@ public void testSimpleGet() throws Exception { } @Test - public void testSimpleGetConnectionClose() throws Exception { + void testSimpleGetConnectionClose() throws Exception { server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); final InetSocketAddress serverEndpoint = server.start(); @@ -261,10 +255,10 @@ public void testSimpleGetConnectionClose() throws Exception { } @Test - public void testSimpleGetIdentityTransfer() throws Exception { + void testSimpleGetIdentityTransfer() throws Exception { server.register("/hello", () -> new SingleLineResponseHandler("Hi there")); - final HttpProcessor httpProcessor = new DefaultHttpProcessor(new RequestValidateHost()); - final InetSocketAddress serverEndpoint = server.start(httpProcessor, Http1Config.DEFAULT); + server.configure(new DefaultHttpProcessor(new RequestValidateHost())); + final InetSocketAddress serverEndpoint = server.start(); client.start(); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/JSSEProviderIntegrationTests.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/JSSEProviderIntegrationTests.java index 36ecda68ad..f168cfc9cd 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/JSSEProviderIntegrationTests.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/JSSEProviderIntegrationTests.java @@ -29,12 +29,14 @@ 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.OS; -public class JSSEProviderIntegrationTests { +class JSSEProviderIntegrationTests { @Nested @DisplayName("Oracle (default)") - public class Oracle extends JSSEProviderIntegrationTest { + class Oracle extends JSSEProviderIntegrationTest { public Oracle() { super("Oracle", null); @@ -44,7 +46,8 @@ public Oracle() { @Nested @DisplayName("Conscrypt (TLSv1.2)") - public class ConscryptTlsV1_2 extends JSSEProviderIntegrationTest { + @DisabledOnOs(OS.MAC) + class ConscryptTlsV1_2 extends JSSEProviderIntegrationTest { public ConscryptTlsV1_2() { super("Conscrypt", "TLSv1.2"); @@ -54,7 +57,8 @@ public ConscryptTlsV1_2() { @Nested @DisplayName("Conscrypt (TLSv1.3)") - public class ConscryptTlsV1_3 extends JSSEProviderIntegrationTest { + @DisabledOnOs(OS.MAC) + class ConscryptTlsV1_3 extends JSSEProviderIntegrationTest { public ConscryptTlsV1_3() { super("Conscrypt", "TLSv1.3"); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/MessageExchangeHandler.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/MessageExchangeHandler.java index ae7a8234cc..80339ed60b 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/MessageExchangeHandler.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/MessageExchangeHandler.java @@ -38,6 +38,7 @@ import org.apache.hc.core5.http.protocol.HttpContext; /** + * @param The message body type. * @since 5.0 */ public abstract class MessageExchangeHandler extends AbstractServerExchangeHandler> { diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/TLSIntegrationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/TLSIntegrationTest.java index 5bf8064bcd..e0acd9685d 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/TLSIntegrationTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/TLSIntegrationTest.java @@ -28,6 +28,7 @@ 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; @@ -41,6 +42,7 @@ import org.apache.hc.core5.concurrent.BasicFuture; 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; @@ -52,7 +54,9 @@ 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; @@ -62,7 +66,6 @@ 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.protocol.UriPatternMatcher; import org.apache.hc.core5.http.ssl.TLS; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.net.NamedEndpoint; @@ -93,7 +96,7 @@ import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.jupiter.params.provider.ValueSource; -public class TLSIntegrationTest { +class TLSIntegrationTest { private static final Timeout TIMEOUT = Timeout.ofSeconds(30); @@ -133,7 +136,6 @@ public void afterEach(final ExtensionContext context) throws Exception { HttpAsyncServer createServer(final TlsStrategy tlsStrategy) { return AsyncServerBootstrap.bootstrap() - .setLookupRegistry(new UriPatternMatcher<>()) .setIOReactorConfig( IOReactorConfig.custom() .setSoTimeout(TIMEOUT) @@ -144,7 +146,10 @@ HttpAsyncServer createServer(final TlsStrategy tlsStrategy) { .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE) .setExceptionCallback(LoggingExceptionCallback.INSTANCE) .setIOSessionListener(LoggingIOSessionListener.INSTANCE) - .register("*", () -> new EchoHandler(2048)) + .setRequestRouter(RequestRouter.>builder() + .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) + .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) + .build()) .create(); } @@ -178,7 +183,7 @@ Future executeTlsHandshake() throws Exception { @Override public void completed(final AsyncClientEndpoint clientEndpoint) { try { - ((TlsUpgradeCapable) clientEndpoint).tlsUpgrade( + ((TlsUpgradeCapable) clientEndpoint). tlsUpgrade( target, new FutureContribution(tlsFuture) { @@ -199,7 +204,7 @@ public void completed(final ProtocolIOSession protocolIOSession) { @ParameterizedTest(name = "TLS protocol {0}") @ArgumentsSource(SupportedTLSProtocolProvider.class) - public void testTLSSuccess(final TLS tlsProtocol) throws Exception { + void testTLSSuccess(final TLS tlsProtocol) throws Exception { final TlsStrategy serverTlsStrategy = new TestTlsStrategy( SSLTestContexts.createServerSSLContext(), (endpoint, sslEngine) -> sslEngine.setEnabledProtocols(new String[]{tlsProtocol.getId()}), @@ -225,7 +230,7 @@ public void testTLSSuccess(final TLS tlsProtocol) throws Exception { } @Test - public void testTLSTrustFailure() throws Exception { + void testTLSTrustFailure() throws Exception { final TlsStrategy serverTlsStrategy = new BasicServerTlsStrategy(SSLTestContexts.createServerSSLContext()); server = createServer(serverTlsStrategy); server.start(); @@ -243,7 +248,7 @@ public void testTLSTrustFailure() throws Exception { } @Test - public void testTLSClientAuthFailure() throws Exception { + void testTLSClientAuthFailure() throws Exception { final TlsStrategy serverTlsStrategy = new BasicServerTlsStrategy( SSLTestContexts.createServerSSLContext(), (endpoint, sslEngine) -> sslEngine.setNeedClientAuth(true), @@ -273,7 +278,7 @@ public void testTLSClientAuthFailure() throws Exception { } @Test - public void testSSLDisabledByDefault() throws Exception { + void testSSLDisabledByDefault() throws Exception { final TlsStrategy serverTlsStrategy = new TestTlsStrategy( SSLTestContexts.createServerSSLContext(), (endpoint, sslEngine) -> sslEngine.setEnabledProtocols(new String[]{"SSLv3"}), @@ -309,7 +314,7 @@ public void testSSLDisabledByDefault() throws Exception { "TLS_KRB5_EXPORT_WITH_RC4_40_SHA", "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5" }) - public void testWeakCipherDisabledByDefault(final String cipher) throws Exception { + void testWeakCipherDisabledByDefault(final String cipher) throws Exception { final TlsStrategy serverTlsStrategy = new TestTlsStrategy( SSLTestContexts.createServerSSLContext(), (endpoint, sslEngine) -> sslEngine.setEnabledCipherSuites(new String[]{cipher}), @@ -328,7 +333,7 @@ public void testWeakCipherDisabledByDefault(final String cipher) throws Exceptio } @Test - public void testTLSVersionMismatch() throws Exception { + void testTLSVersionMismatch() throws Exception { final TlsStrategy serverTlsStrategy = new TestTlsStrategy( SSLTestContexts.createServerSSLContext(), (endpoint, sslEngine) -> { @@ -357,6 +362,40 @@ public void testTLSVersionMismatch() throws Exception { 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(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.getLocalHost(), "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.getLocalHost(), "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(); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/TLSUpgradeTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/TLSUpgradeTest.java index d3ac4796d5..1c554e33c5 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/TLSUpgradeTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/TLSUpgradeTest.java @@ -34,6 +34,7 @@ import org.apache.hc.core5.concurrent.BasicFuture; 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; @@ -43,26 +44,27 @@ 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.http.protocol.UriPatternMatcher; 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.nio.extension.HttpAsyncRequesterResource; -import org.apache.hc.core5.testing.nio.extension.HttpAsyncServerResource; +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; -public class TLSUpgradeTest { +class TLSUpgradeTest { private static final Timeout TIMEOUT = Timeout.ofSeconds(30); @@ -77,8 +79,10 @@ public TLSUpgradeTest() { IOReactorConfig.custom() .setSoTimeout(TIMEOUT) .build()) - .setLookupRegistry(new UriPatternMatcher<>()) - .register("*", () -> new EchoHandler(2048)) + .setRequestRouter(RequestRouter.>builder() + .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new EchoHandler(2048)) + .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) + .build()) ); this.clientResource = new HttpAsyncRequesterResource(bootstrap -> bootstrap .setIOReactorConfig(IOReactorConfig.custom() @@ -88,7 +92,7 @@ public TLSUpgradeTest() { } @Test - public void testTLSUpgrade() throws Exception { + void testTLSUpgrade() throws Exception { final HttpAsyncServer server = serverResource.start(); final Future future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS); final ListenerEndpoint listener = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/TestDefaultListeningIOReactor.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/TestDefaultListeningIOReactor.java index 82a5f0e4d4..d183d391bb 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/TestDefaultListeningIOReactor.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/TestDefaultListeningIOReactor.java @@ -46,12 +46,12 @@ /** * Basic tests for {@link DefaultListeningIOReactor}. */ -public class TestDefaultListeningIOReactor { +class TestDefaultListeningIOReactor { private DefaultListeningIOReactor ioReactor; @BeforeEach - public void setup() throws Exception { + void setup() { final IOReactorConfig reactorConfig = IOReactorConfig.custom() .setIoThreadCount(1) .build(); @@ -59,14 +59,14 @@ public void setup() throws Exception { } @AfterEach - public void cleanup() throws Exception { + void cleanup() { if (this.ioReactor != null) { this.ioReactor.close(CloseMode.IMMEDIATE); } } @Test - public void testEndpointUpAndDown() throws Exception { + void testEndpointUpAndDown() throws Exception { ioReactor.start(); Set endpoints = ioReactor.getEndpoints(); @@ -100,7 +100,7 @@ public void testEndpointUpAndDown() throws Exception { } @Test - public void testEndpointAlreadyBound() throws Exception { + void testEndpointAlreadyBound() throws Exception { ioReactor.start(); final Future future1 = ioReactor.listen(new InetSocketAddress(0)); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/reactive/IntegrationTests.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/reactive/IntegrationTests.java index 119f691bd3..d48924fd12 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/reactive/IntegrationTests.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/reactive/IntegrationTests.java @@ -31,11 +31,11 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; -public class IntegrationTests { +class IntegrationTests { @Nested @DisplayName("Reactive client (HTTP/1.1)") - public class CoreFunctions extends ReactiveClientTest { + class CoreFunctions extends ReactiveClientTest { public CoreFunctions() { super(HttpVersionPolicy.FORCE_HTTP_1); @@ -45,7 +45,7 @@ public CoreFunctions() { @Nested @DisplayName("Reactive client (HTTP/2)") - public class CoreFunctionsTls extends ReactiveClientTest { + class CoreFunctionsTls extends ReactiveClientTest { public CoreFunctionsTls() { super(HttpVersionPolicy.FORCE_HTTP_2); diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/reactive/ReactiveClientTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/reactive/ReactiveClientTest.java index 99a572e3a2..e4bf3a9485 100644 --- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/reactive/ReactiveClientTest.java +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/reactive/ReactiveClientTest.java @@ -47,6 +47,7 @@ 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; @@ -54,6 +55,8 @@ 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.reactive.ReactiveEntityProducer; @@ -61,8 +64,8 @@ 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.nio.extension.H2AsyncRequesterResource; -import org.apache.hc.core5.testing.nio.extension.H2AsyncServerResource; +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; @@ -72,7 +75,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.reactivestreams.Publisher; -public abstract class ReactiveClientTest { +abstract class ReactiveClientTest { private static final Timeout SOCKET_TIMEOUT = Timeout.ofSeconds(30); private static final Timeout RESULT_TIMEOUT = Timeout.ofSeconds(60); @@ -93,7 +96,10 @@ public ReactiveClientTest(final HttpVersionPolicy httpVersionPolicy) { IOReactorConfig.custom() .setSoTimeout(SOCKET_TIMEOUT) .build()) - .register("*", () -> new ReactiveServerExchangeHandler(new ReactiveEchoProcessor())) + .setRequestRouter(RequestRouter.>builder() + .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", () -> new ReactiveServerExchangeHandler(new ReactiveEchoProcessor())) + .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) + .build()) ); this.clientResource = new H2AsyncRequesterResource(bootstrap -> bootstrap .setVersionPolicy(versionPolicy) @@ -104,7 +110,7 @@ public ReactiveClientTest(final HttpVersionPolicy httpVersionPolicy) { } @Test - public void testSimpleRequest() throws Exception { + void testSimpleRequest() throws Exception { final InetSocketAddress address = startServer(); final HttpAsyncRequester requester = clientResource.start(); final byte[] input = new byte[1024]; @@ -135,7 +141,7 @@ private BasicRequestProducer getRequestProducer(final InetSocketAddress address, } @Test - public void testLongRunningRequest() throws Exception { + void testLongRunningRequest() throws Exception { final InetSocketAddress address = startServer(); final HttpAsyncRequester requester = clientResource.start(); final long expectedLength = 6_554_200L; @@ -155,7 +161,7 @@ public void testLongRunningRequest() throws Exception { } @Test - public void testManySmallBuffers() throws Exception { + 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 @@ -182,7 +188,7 @@ public void testManySmallBuffers() throws Exception { } @Test - public void testRequestError() throws Exception { + void testRequestError() throws Exception { final InetSocketAddress address = startServer(); final HttpAsyncRequester requester = clientResource.start(); final RuntimeException exceptionThrown = new RuntimeException("Test"); @@ -202,10 +208,10 @@ public void testRequestError() throws Exception { } @Test - public void testRequestTimeout() throws Exception { + void testRequestTimeout() throws Exception { final InetSocketAddress address = startServer(); final HttpAsyncRequester requester = clientResource.start(); - final AtomicBoolean requestPublisherWasCancelled = new AtomicBoolean(false); + final AtomicBoolean requestPublisherWasCancelled = new AtomicBoolean(); final Publisher publisher = Flowable.never() .doOnCancel(() -> requestPublisherWasCancelled.set(true)); final ReactiveEntityProducer producer = new ReactiveEntityProducer(publisher, -1, null, null); @@ -228,10 +234,10 @@ public void testRequestTimeout() throws Exception { } @Test - public void testResponseCancellation() throws Exception { + void testResponseCancellation() throws Exception { final InetSocketAddress address = startServer(); final HttpAsyncRequester requester = clientResource.start(); - final AtomicBoolean requestPublisherWasCancelled = new AtomicBoolean(false); + final AtomicBoolean requestPublisherWasCancelled = new AtomicBoolean(); final AtomicReference requestStreamError = new AtomicReference<>(); final Publisher stream = Reactive3TestUtils.produceStream(Long.MAX_VALUE, 1024, null) .doOnCancel(() -> requestPublisherWasCancelled.set(true)) @@ -244,7 +250,7 @@ public void testResponseCancellation() throws Exception { final Message> response = consumer.getResponseFuture() .get(RESULT_TIMEOUT.getDuration(), RESULT_TIMEOUT.getTimeUnit()); - final AtomicBoolean responsePublisherWasCancelled = new AtomicBoolean(false); + final AtomicBoolean responsePublisherWasCancelled = new AtomicBoolean(); final List outputBuffers = Flowable.fromPublisher(response.getBody()) .doOnCancel(() -> responsePublisherWasCancelled.set(true)) .take(3) diff --git a/httpcore5/pom.xml b/httpcore5/pom.xml index 2965f67403..effe78481f 100644 --- a/httpcore5/pom.xml +++ b/httpcore5/pom.xml @@ -28,7 +28,7 @@ org.apache.httpcomponents.core5 httpcore5-parent - 5.2.4-SNAPSHOT + 5.3 httpcore5 Apache HttpComponents Core HTTP/1.1 diff --git a/httpcore5/src/main/java/org/apache/hc/core5/annotation/Contract.java b/httpcore5/src/main/java/org/apache/hc/core5/annotation/Contract.java index 8a87a25852..20177c4e55 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/annotation/Contract.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/annotation/Contract.java @@ -33,13 +33,21 @@ import java.lang.annotation.Target; /** - * This annotation defines behavioral contract enforced at runtime by instances of annotated classes. + * 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; } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/concurrent/BasicFuture.java b/httpcore5/src/main/java/org/apache/hc/core5/concurrent/BasicFuture.java index 447d2266e7..6304b2f6e3 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/concurrent/BasicFuture.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/concurrent/BasicFuture.java @@ -31,6 +31,8 @@ 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; @@ -52,9 +54,19 @@ public class BasicFuture implements Future, Cancellable { 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 @@ -78,46 +90,59 @@ private T getResult() throws ExecutionException { } @Override - public synchronized T get() throws InterruptedException, ExecutionException { - while (!this.completed) { - wait(); + public T get() throws InterruptedException, ExecutionException { + lock.lock(); + try { + while (!this.completed) { + condition.await(); + } + return getResult(); + } finally { + lock.unlock(); } - return getResult(); } @Override - public synchronized T get(final long timeout, final TimeUnit unit) + 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; - if (this.completed) { - return getResult(); - } else if (waitTime <= 0) { - throw TimeoutValueException.fromMilliseconds(msecs, msecs + Math.abs(waitTime)); - } else { - for (;;) { - wait(waitTime); - if (this.completed) { - return getResult(); - } - waitTime = msecs - (System.currentTimeMillis() - startTime); - if (waitTime <= 0) { - throw TimeoutValueException.fromMilliseconds(msecs, msecs + Math.abs(waitTime)); + 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) { - synchronized(this) { + lock.lock(); + try { if (this.completed) { return false; } this.completed = true; this.result = result; - notifyAll(); + condition.signalAll(); + } finally { + lock.unlock(); } if (this.callback != null) { this.callback.completed(result); @@ -126,13 +151,16 @@ public boolean completed(final T result) { } public boolean failed(final Exception exception) { - synchronized(this) { + lock.lock(); + try { if (this.completed) { return false; } this.completed = true; this.ex = exception; - notifyAll(); + condition.signalAll(); + } finally { + lock.unlock(); } if (this.callback != null) { this.callback.failed(exception); @@ -142,13 +170,16 @@ public boolean failed(final Exception exception) { @Override public boolean cancel(final boolean mayInterruptIfRunning) { - synchronized(this) { + lock.lock(); + try { if (this.completed) { return false; } this.completed = true; this.cancelled = true; - notifyAll(); + condition.signalAll(); + } finally { + lock.unlock(); } if (this.callback != null) { this.callback.cancelled(); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/concurrent/CallbackContribution.java b/httpcore5/src/main/java/org/apache/hc/core5/concurrent/CallbackContribution.java index 4dc602f838..90e3f1a1ea 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/concurrent/CallbackContribution.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/concurrent/CallbackContribution.java @@ -27,7 +27,7 @@ package org.apache.hc.core5.concurrent; /** - * Convenience base class for {@link FutureCallback}s that contribute a result + * 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. @@ -37,6 +37,11 @@ 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; } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/concurrent/CancellableDependency.java b/httpcore5/src/main/java/org/apache/hc/core5/concurrent/CancellableDependency.java index 6074558638..3fb21141c4 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/concurrent/CancellableDependency.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/concurrent/CancellableDependency.java @@ -37,6 +37,8 @@ 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); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/concurrent/FutureCallback.java b/httpcore5/src/main/java/org/apache/hc/core5/concurrent/FutureCallback.java index b606be7cf2..5f2b05d2f5 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/concurrent/FutureCallback.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/concurrent/FutureCallback.java @@ -27,10 +27,9 @@ package org.apache.hc.core5.concurrent; /** - * A callback interface that gets invoked upon completion of - * a {@link java.util.concurrent.Future}. + * A callback interface that gets invoked upon completion of a {@link java.util.concurrent.Future}. * - * @param the future result type returned by this callback. + * @param the future result type consumed by this callback. * @since 4.2 */ public interface FutureCallback { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/concurrent/FutureContribution.java b/httpcore5/src/main/java/org/apache/hc/core5/concurrent/FutureContribution.java index 8960b4adb4..88cd68b5b2 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/concurrent/FutureContribution.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/concurrent/FutureContribution.java @@ -37,6 +37,11 @@ public abstract class FutureContribution implements FutureCallback { private final BasicFuture future; + /** + * Constructs a new instance to callback the given {@link BasicFuture}. + * + * @param future The callback. + */ public FutureContribution(final BasicFuture future) { this.future = future; } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/function/Callback.java b/httpcore5/src/main/java/org/apache/hc/core5/function/Callback.java index 37168d0780..1cc35b881e 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/function/Callback.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/function/Callback.java @@ -30,6 +30,7 @@ /** * Abstract callback. * + * @param The type of object consumed by the callback. * @since 5.0 */ @FunctionalInterface diff --git a/httpcore5/src/main/java/org/apache/hc/core5/function/Decorator.java b/httpcore5/src/main/java/org/apache/hc/core5/function/Decorator.java index 9390b49618..99ed0d1517 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/function/Decorator.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/function/Decorator.java @@ -30,6 +30,7 @@ /** * Abstract decorator. * + * @param The type of object to decorate. * @since 5.0 */ @FunctionalInterface diff --git a/httpcore5/src/main/java/org/apache/hc/core5/function/Factory.java b/httpcore5/src/main/java/org/apache/hc/core5/function/Factory.java index fa55cff17b..e14d783c0b 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/function/Factory.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/function/Factory.java @@ -30,6 +30,8 @@ /** * Abstract object factory. * + * @param

The factory's input type. + * @param The type of object produced by this factory. * @since 5.0 */ @FunctionalInterface diff --git a/httpcore5/src/main/java/org/apache/hc/core5/function/Resolver.java b/httpcore5/src/main/java/org/apache/hc/core5/function/Resolver.java index 5a678949d2..8c7e4e01a3 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/function/Resolver.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/function/Resolver.java @@ -30,6 +30,8 @@ /** * Abstract resolver from input to output. * + * @param the input type. + * @param the output type. * @since 5.0 */ @FunctionalInterface diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/Chars.java b/httpcore5/src/main/java/org/apache/hc/core5/http/Chars.java index 355aaefccc..7b1a35e2d1 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/Chars.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/Chars.java @@ -34,6 +34,7 @@ */ 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; // diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/ContentType.java b/httpcore5/src/main/java/org/apache/hc/core5/http/ContentType.java index 938fc10e20..b8e049961a 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/ContentType.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/ContentType.java @@ -37,12 +37,12 @@ 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.BasicHeaderValueFormatter; -import org.apache.hc.core5.http.message.BasicHeaderValueParser; 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; @@ -153,7 +153,7 @@ public final class ContentType implements Serializable { "multipart/related", StandardCharsets.ISO_8859_1); public static final ContentType TEXT_HTML = create( - "text/html", StandardCharsets.ISO_8859_1); + "text/html", StandardCharsets.UTF_8); /** * Public constant media type for {@code text/markdown}. @@ -163,7 +163,7 @@ public final class ContentType implements Serializable { "text/markdown", StandardCharsets.UTF_8); public static final ContentType TEXT_PLAIN = create( - "text/plain", StandardCharsets.ISO_8859_1); + "text/plain", StandardCharsets.UTF_8); public static final ContentType TEXT_XML = create( "text/xml", StandardCharsets.UTF_8); /** @@ -259,6 +259,10 @@ public Charset getCharset(final 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) { @@ -284,7 +288,7 @@ public String toString() { buf.append(this.mimeType); if (this.params != null) { buf.append("; "); - BasicHeaderValueFormatter.INSTANCE.formatParameters(buf, this.params, false); + MessageSupport.formatParameters(buf, this.params); } else if (this.charset != null) { buf.append("; charset="); buf.append(this.charset.name()); @@ -380,7 +384,8 @@ private static ContentType create(final String mimeType, final NameValuePair[] p * 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( @@ -419,9 +424,11 @@ private static ContentType parse(final CharSequence s, final boolean strict) thr return null; } final ParserCursor cursor = new ParserCursor(0, s.length()); - final HeaderElement[] elements = BasicHeaderValueParser.INSTANCE.parseElements(s, cursor); - if (elements.length > 0) { - return create(elements[0], strict); + 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; } @@ -485,6 +492,8 @@ public ContentType withCharset(final String charset) { * * @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( diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/EntityDetails.java b/httpcore5/src/main/java/org/apache/hc/core5/http/EntityDetails.java index d46325c2cd..69399dc1bb 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/EntityDetails.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/EntityDetails.java @@ -37,31 +37,40 @@ public interface EntityDetails { /** - * Returns length of the entity, if known. + * Gets length of this entity, if known. + * + * @return the length of this entity, may be {@code 0}. */ long getContentLength(); /** - * Returns content type of the entity, if known. + * Gets content type of this entity, if known. + * + * @return the content type of this entity, may be {@code null}. */ String getContentType(); /** - * Returns content encoding of the entity, if known. + * Gets content encoding of this entity, if known. + * + * @return the content encoding of this entity, may be {@code null}. */ String getContentEncoding(); /** - * Returns chunked transfer hint for this entity. + * 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(); /** - * Preliminary declaration of trailing headers. + * Gets the preliminary declaration of trailing headers. + * + * @return the preliminary declaration of trailing headers. */ Set getTrailerNames(); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpEntity.java b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpEntity.java index 331aff5868..36aa70536e 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpEntity.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpEntity.java @@ -69,7 +69,7 @@ public interface HttpEntity extends EntityDetails, Closeable { /** - * Tells if the entity is capable of producing its data more than once. + * 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. @@ -77,7 +77,7 @@ public interface HttpEntity extends EntityDetails, Closeable { boolean isRepeatable(); /** - * Returns a content stream of the entity. + * 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. @@ -102,7 +102,7 @@ public interface HttpEntity extends EntityDetails, Closeable { * * @throws IOException if the stream could not be created * @throws UnsupportedOperationException - * if entity content cannot be represented as {@link java.io.InputStream}. + * if entity content cannot be represented as {@link InputStream}. * * @see #isRepeatable() */ @@ -123,7 +123,7 @@ public interface HttpEntity extends EntityDetails, Closeable { void writeTo(OutputStream outStream) throws IOException; /** - * Tells whether this entity depends on an underlying stream. + * 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 @@ -135,7 +135,7 @@ public interface HttpEntity extends EntityDetails, Closeable { boolean isStreaming(); // don't expect an exception here /** - * Returns supplier of message trailers - headers sent after message body. + * Gets supplier of message trailers - headers sent after message body. * May return {@code null} if trailers are not available. * * @since 5.0 diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpHost.java b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpHost.java index 52938cddba..018c543b3b 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpHost.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpHost.java @@ -137,6 +137,7 @@ public HttpHost(final String scheme, final String hostname) { /** * 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 { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpMessage.java b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpMessage.java index cc5bab13b6..1aab982624 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpMessage.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpMessage.java @@ -41,7 +41,9 @@ public interface HttpMessage extends MessageHeaders { * 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); @@ -52,7 +54,9 @@ public interface HttpMessage extends MessageHeaders { * 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(); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpRequest.java b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpRequest.java index 5b37b70c7b..d22f7379fe 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpRequest.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpRequest.java @@ -58,6 +58,7 @@ public interface HttpRequest extends HttpMessage { /** * Sets URI path of this request message. * + * @param path The URI path of this request message. * @since 5.0 */ void setPath(String path); @@ -66,7 +67,6 @@ public interface HttpRequest extends HttpMessage { * Returns scheme of this request message. * * @return the scheme or {@code null}. - * * @since 5.0 */ String getScheme(); @@ -74,6 +74,7 @@ public interface HttpRequest extends HttpMessage { /** * Sets scheme of this request message. * + * @param scheme The scheme of this request message. * @since 5.0 */ void setScheme(String scheme); @@ -82,7 +83,6 @@ public interface HttpRequest extends HttpMessage { * Returns authority of this request message. * * @return the authority or {@code null}. - * * @since 5.0 */ URIAuthority getAuthority(); @@ -90,6 +90,7 @@ public interface HttpRequest extends HttpMessage { /** * Sets authority of this request message. * + * @param authority The authority of this request message. * @since 5.0 */ void setAuthority(URIAuthority authority); @@ -99,7 +100,6 @@ public interface HttpRequest extends HttpMessage { * Applicable to HTTP/1.1 version or earlier. * * @return the request URI. - * * @since 5.0 */ String getRequestUri(); @@ -108,7 +108,7 @@ public interface HttpRequest extends HttpMessage { * 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; @@ -117,7 +117,6 @@ public interface HttpRequest extends HttpMessage { * Sets the full request URI of this request message. * * @param requestUri the request URI. - * * @since 5.0 */ void setUri(final URI requestUri); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpRequestFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpRequestFactory.java index 00c1ecd831..6f0dc1261e 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpRequestFactory.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpRequestFactory.java @@ -32,6 +32,7 @@ /** * A factory for {@link HttpRequest} objects. * + * @param The type of {@link HttpRequest}. * @since 4.0 */ public interface HttpRequestFactory { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpRequestMapper.java b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpRequestMapper.java index 15431ba0f5..54f5a86dcb 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpRequestMapper.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpRequestMapper.java @@ -33,6 +33,7 @@ * 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 { @@ -41,8 +42,8 @@ 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. + * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpResponse.java b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpResponse.java index 9b67eee8b2..80c86e496a 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpResponse.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpResponse.java @@ -41,6 +41,8 @@ public interface HttpResponse extends HttpMessage { * Obtains the code of this response message. * * @return the status code. + * + * @see HttpStatus */ int getCode(); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpResponseFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpResponseFactory.java index 2c7d7d8be3..92a6db2dfc 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpResponseFactory.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpResponseFactory.java @@ -30,6 +30,7 @@ /** * A factory for {@link HttpResponse} objects. * + * @param The type of {@link HttpResponse}. * @since 4.0 */ public interface HttpResponseFactory { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpStatus.java b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpStatus.java index 02b448c1a0..efc1c96f32 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpStatus.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpStatus.java @@ -29,22 +29,10 @@ /** * Constants enumerating the HTTP status codes. - * All status codes defined in RFC 7231 (HTTP/1.1), RFC 2518 (WebDAV), RFC 7540 (HTTP/2), - * RFC 6585 (Additional HTTP Status Codes), RFC 8297 (Early Hints), RFC 7538 (Permanent Redirect), - * RFC 7725 (An HTTP Status Code to Report Legal Obstacles) and RFC 2295 (Transparent Content - * Negotiation) are listed. * - * @see RFC 7231 (HTTP/1.1) - * @see RFC 2518 (WebDAV) - * @see RFC 7540 (HTTP/2) - * @see RFC 6585 (Additional HTTP Status Codes) - * @see RFC 8297 (Early Hints) - * @see RFC 7538 (Permanent Redirect) - * @see RFC 7725 (An HTTP Status Code to Report Legal Obstacles) - * @see RFC 2295 (Transparent Content Negotiation) - * @see RFC 2817 (Upgrading to TLS Within HTTP/1.1) - * @see RFC 8470 (Using Early Data in HTTP) * @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 { @@ -53,12 +41,12 @@ private HttpStatus() { } // --- 1xx Informational --- - /** {@code 100 1xx Informational} (HTTP/1.1 - RFC 7231) */ + /** {@code 100 1xx Informational} (HTTP Semantics) */ public static final int SC_INFORMATIONAL = 100; - /** {@code 100 Continue} (HTTP/1.1 - RFC 7231) */ + /** {@code 100 Continue} (HTTP Semantics) */ public static final int SC_CONTINUE = 100; - /** {@code 101 Switching Protocols} (HTTP/1.1 - RFC 7231)*/ + /** {@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; @@ -66,22 +54,22 @@ private HttpStatus() { public static final int SC_EARLY_HINTS = 103; // --- 2xx Success --- - /** {@code 2xx Success} (HTTP/1.0 - RFC 7231) */ + /** {@code 2xx Success} (HTTP Semantics) */ public static final int SC_SUCCESS = 200; - /** {@code 200 OK} (HTTP/1.0 - RFC 7231) */ + /** {@code 200 OK} (HTTP Semantics) */ public static final int SC_OK = 200; - /** {@code 201 Created} (HTTP/1.0 - RFC 7231) */ + /** {@code 201 Created} (HTTP Semantics) */ public static final int SC_CREATED = 201; - /** {@code 202 Accepted} (HTTP/1.0 - RFC 7231) */ + /** {@code 202 Accepted} (HTTP Semantics) */ public static final int SC_ACCEPTED = 202; - /** {@code 203 Non Authoritative Information} (HTTP/1.1 - RFC 7231) */ + /** {@code 203 Non Authoritative Information} (HTTP Semantics) */ public static final int SC_NON_AUTHORITATIVE_INFORMATION = 203; - /** {@code 204 No Content} (HTTP/1.0 - RFC 7231) */ + /** {@code 204 No Content} (HTTP Semantics) */ public static final int SC_NO_CONTENT = 204; - /** {@code 205 Reset Content} (HTTP/1.1 - RFC 7231) */ + /** {@code 205 Reset Content} (HTTP Semantics) */ public static final int SC_RESET_CONTENT = 205; - /** {@code 206 Partial Content} (HTTP/1.1 - RFC 7231) */ + /** {@code 206 Partial Content} (HTTP Semantics) */ public static final int SC_PARTIAL_CONTENT = 206; /** * {@code 207 Multi-Status} (WebDAV - RFC 2518) @@ -99,69 +87,73 @@ private HttpStatus() { public static final int SC_IM_USED = 226; // --- 3xx Redirection --- - /** {@code 3xx Redirection} (HTTP/1.1 - RFC 7231) */ + /** {@code 3xx Redirection} (HTTP Semantics) */ public static final int SC_REDIRECTION = 300; - /** {@code 300 Multiple Choices} (HTTP/1.1 - RFC 7231) */ + /** {@code 300 Multiple Choices} (HTTP Semantics) */ public static final int SC_MULTIPLE_CHOICES = 300; - /** {@code 301 Moved Permanently} (HTTP/1.0 - RFC 7231) */ + /** {@code 301 Moved Permanently} (HTTP Semantics) */ public static final int SC_MOVED_PERMANENTLY = 301; - /** {@code 302 Moved Temporarily} (Sometimes {@code Found}) (HTTP/1.0 - RFC 7231) */ + /** {@code 302 Moved Temporarily} (Sometimes {@code Found}) (HTTP Semantics) */ public static final int SC_MOVED_TEMPORARILY = 302; - /** {@code 303 See Other} (HTTP/1.1 - RFC 7231) */ + /** {@code 303 See Other} (HTTP Semantics) */ public static final int SC_SEE_OTHER = 303; - /** {@code 304 Not Modified} (HTTP/1.0 - RFC 7231) */ + /** {@code 304 Not Modified} (HTTP Semantics) */ public static final int SC_NOT_MODIFIED = 304; - /** {@code 305 Use Proxy} (HTTP/1.1 - RFC 7231) */ + /** {@code 305 Use Proxy} (HTTP Semantics) */ public static final int SC_USE_PROXY = 305; - /** {@code 307 Temporary Redirect} (HTTP/1.1 - RFC 7231) */ + /** {@code 307 Temporary Redirect} (HTTP Semantics) */ public static final int SC_TEMPORARY_REDIRECT = 307; - /** {@code 308 Permanent Redirect} (HTTP/1.1 - RFC 7538) */ + /** {@code 308 Permanent Redirect} (HTTP Semantics) */ public static final int SC_PERMANENT_REDIRECT = 308; // --- 4xx Client Error --- - /** {@code 4xx Client Error} (HTTP/1.1 - RFC 7231) */ + /** {@code 4xx Client Error} (HTTP Semantics) */ public static final int SC_CLIENT_ERROR = 400; - /** {@code 400 Bad Request} (HTTP/1.1 - RFC 7231) */ + /** {@code 400 Bad Request} (HTTP Semantics) */ public static final int SC_BAD_REQUEST = 400; - /** {@code 401 Unauthorized} (HTTP/1.0 - RFC 7231) */ + /** {@code 401 Unauthorized} (HTTP Semantics) */ public static final int SC_UNAUTHORIZED = 401; - /** {@code 402 Payment Required} (HTTP/1.1 - RFC 7231) */ + /** {@code 402 Payment Required} (HTTP Semantics) */ public static final int SC_PAYMENT_REQUIRED = 402; - /** {@code 403 Forbidden} (HTTP/1.0 - RFC 7231) */ + /** {@code 403 Forbidden} (HTTP Semantics) */ public static final int SC_FORBIDDEN = 403; - /** {@code 404 Not Found} (HTTP/1.0 - RFC 7231) */ + /** {@code 404 Not Found} (HTTP Semantics) */ public static final int SC_NOT_FOUND = 404; - /** {@code 405 Method Not Allowed} (HTTP/1.1 - RFC 7231) */ + /** {@code 405 Method Not Allowed} (HTTP Semantics) */ public static final int SC_METHOD_NOT_ALLOWED = 405; - /** {@code 406 Not Acceptable} (HTTP/1.1 - RFC 7231) */ + /** {@code 406 Not Acceptable} (HTTP Semantics) */ public static final int SC_NOT_ACCEPTABLE = 406; - /** {@code 407 Proxy Authentication Required} (HTTP/1.1 - RFC 7231)*/ + /** {@code 407 Proxy Authentication Required} (HTTP Semantics)*/ public static final int SC_PROXY_AUTHENTICATION_REQUIRED = 407; - /** {@code 408 Request Timeout} (HTTP/1.1 - RFC 7231) */ + /** {@code 408 Request Timeout} (HTTP Semantics) */ public static final int SC_REQUEST_TIMEOUT = 408; - /** {@code 409 Conflict} (HTTP/1.1 - RFC 7231) */ + /** {@code 409 Conflict} (HTTP Semantics) */ public static final int SC_CONFLICT = 409; - /** {@code 410 Gone} (HTTP/1.1 - RFC 7231) */ + /** {@code 410 Gone} (HTTP Semantics) */ public static final int SC_GONE = 410; - /** {@code 411 Length Required} (HTTP/1.1 - RFC 7231) */ + /** {@code 411 Length Required} (HTTP Semantics) */ public static final int SC_LENGTH_REQUIRED = 411; - /** {@code 412 Precondition Failed} (HTTP/1.1 - RFC 7231) */ + /** {@code 412 Precondition Failed} (HTTP Semantics) */ public static final int SC_PRECONDITION_FAILED = 412; - /** {@code 413 Request Entity Too Large} (HTTP/1.1 - RFC 7231) */ + /** {@code 413 Request Entity Too Large} (HTTP Semantics) */ public static final int SC_REQUEST_TOO_LONG = 413; - /** {@code 414 Request-URI Too Long} (HTTP/1.1 - RFC 7231) */ + /** {@code 414 Request-URI Too Long} (HTTP Semantics) */ public static final int SC_REQUEST_URI_TOO_LONG = 414; - /** {@code 415 Unsupported Media Type} (HTTP/1.1 - RFC 7231) */ + /** {@code 415 Unsupported Media Type} (HTTP Semantics) */ public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415; - /** {@code 416 Requested Range Not Satisfiable} (HTTP/1.1 - RFC 7231) */ + /** {@code 416 Requested Range Not Satisfiable} (HTTP Semantics) */ public static final int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416; - /** {@code 417 Expectation Failed} (HTTP/1.1 - RFC 7231) */ + /** {@code 417 Expectation Failed} (HTTP Semantics) */ public static final int SC_EXPECTATION_FAILED = 417; - /** {@code 421 Misdirected Request} (HTTP/2 - RFC 7540) */ + /** {@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. @@ -177,16 +169,17 @@ private HttpStatus() { * (WebDAV - draft-ietf-webdav-protocol-05?) */ public static final int SC_METHOD_FAILURE = 420; - /** {@code 422 Unprocessable Entity} (WebDAV - RFC 2518) */ - public static final int SC_UNPROCESSABLE_ENTITY = 422; + /** + * @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 426 Upgrade Dependency} (HTTP/1.1 - RFC 2817) */ - public static final int SC_UPGRADE_REQUIRED = 426; /** {@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) */ @@ -197,20 +190,20 @@ private HttpStatus() { public static final int SC_UNAVAILABLE_FOR_LEGAL_REASONS = 451; // --- 5xx Server Error --- - /** {@code 500 Server Error} (HTTP/1.0 - RFC 7231) */ + /** {@code 500 Server Error} (HTTP Semantics) */ public static final int SC_SERVER_ERROR = 500; - /** {@code 500 Internal Server Error} (HTTP/1.0 - RFC 7231) */ + /** {@code 500 Internal Server Error} (HTTP Semantics) */ public static final int SC_INTERNAL_SERVER_ERROR = 500; - /** {@code 501 Not Implemented} (HTTP/1.0 - RFC 7231) */ + /** {@code 501 Not Implemented} (HTTP Semantics) */ public static final int SC_NOT_IMPLEMENTED = 501; - /** {@code 502 Bad Gateway} (HTTP/1.0 - RFC 7231) */ + /** {@code 502 Bad Gateway} (HTTP Semantics) */ public static final int SC_BAD_GATEWAY = 502; - /** {@code 503 Service Unavailable} (HTTP/1.0 - RFC 7231) */ + /** {@code 503 Service Unavailable} (HTTP Semantics) */ public static final int SC_SERVICE_UNAVAILABLE = 503; - /** {@code 504 Gateway Timeout} (HTTP/1.1 - RFC 7231) */ + /** {@code 504 Gateway Timeout} (HTTP Semantics) */ public static final int SC_GATEWAY_TIMEOUT = 504; - /** {@code 505 HTTP Version Not Supported} (HTTP/1.1 - RFC 7231) */ + /** {@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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpVersion.java b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpVersion.java index c9a3b1082e..a8282f5a5c 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpVersion.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpVersion.java @@ -80,10 +80,14 @@ public final class HttpVersion extends ProtocolVersion { * @since 5.0 */ public static HttpVersion get(final int major, final int minor) { - for (int i = 0; i < ALL.length; i++) { - if (ALL[i].equals(major, minor)) { - return ALL[i]; + 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); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/Message.java b/httpcore5/src/main/java/org/apache/hc/core5/http/Message.java index f726291596..b60d9724b1 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/Message.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/Message.java @@ -42,25 +42,48 @@ 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 + - ']'; + return "[head=" + head + ", body=" + body + ']'; } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/Method.java b/httpcore5/src/main/java/org/apache/hc/core5/http/Method.java index 0b3b62795a..731e72d723 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/Method.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/Method.java @@ -32,20 +32,65 @@ import org.apache.hc.core5.util.Args; /** - * Common HTTP methods defined by the HTTP spec. + * 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); private final boolean safe; @@ -56,10 +101,20 @@ public enum Method { 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; } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/ParseException.java b/httpcore5/src/main/java/org/apache/hc/core5/http/ParseException.java index 1f44a7d4d3..f2d4a61547 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/ParseException.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/ParseException.java @@ -39,7 +39,7 @@ public class ParseException extends ProtocolException { private final int errorOffset; /** - * Creates a {@link ParseException} without details. + * Constructs a new {@link ParseException} without details. */ public ParseException() { super(); @@ -47,7 +47,7 @@ public ParseException() { } /** - * Creates a {@link ParseException} with a detail message. + * Constructs a new {@link ParseException} with a detail message. * * @param message the exception detail message, or {@code null} */ @@ -57,7 +57,7 @@ public ParseException(final String message) { } /** - * Creates a {@link ParseException} with parsing context details. + * Constructs a new {@link ParseException} with parsing context details. * * @since 5.0 */ @@ -69,7 +69,7 @@ public ParseException(final String description, final CharSequence text, final i } /** - * Creates a {@link ParseException} with parsing context details. + * Constructs a new {@link ParseException} with parsing context details. * * @since 5.0 */ diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/ProtocolVersion.java b/httpcore5/src/main/java/org/apache/hc/core5/http/ProtocolVersion.java index 572bbc5d91..b6247e9535 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/ProtocolVersion.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/ProtocolVersion.java @@ -30,8 +30,10 @@ 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 @@ -239,6 +241,29 @@ 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. diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/ProtocolVersionParser.java b/httpcore5/src/main/java/org/apache/hc/core5/http/ProtocolVersionParser.java new file mode 100644 index 0000000000..9a694a594f --- /dev/null +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/ProtocolVersionParser.java @@ -0,0 +1,127 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * 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"); + } + } + +} diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/config/CharCodingConfig.java b/httpcore5/src/main/java/org/apache/hc/core5/http/config/CharCodingConfig.java index 5c9e74982e..2dd82b85fa 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/config/CharCodingConfig.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/config/CharCodingConfig.java @@ -45,6 +45,8 @@ 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; @@ -110,7 +112,7 @@ public Builder setCharset(final Charset charset) { public Builder setMalformedInputAction(final CodingErrorAction malformedInputAction) { this.malformedInputAction = malformedInputAction; if (malformedInputAction != null && this.charset == null) { - this.charset = StandardCharsets.US_ASCII; + this.charset = DEFAULT_CHARSET; } return this; } @@ -118,7 +120,7 @@ public Builder setMalformedInputAction(final CodingErrorAction malformedInputAct public Builder setUnmappableInputAction(final CodingErrorAction unmappableInputAction) { this.unmappableInputAction = unmappableInputAction; if (unmappableInputAction != null && this.charset == null) { - this.charset = StandardCharsets.US_ASCII; + this.charset = DEFAULT_CHARSET; } return this; } @@ -126,7 +128,7 @@ public Builder setUnmappableInputAction(final CodingErrorAction unmappableInputA public CharCodingConfig build() { Charset charsetCopy = charset; if (charsetCopy == null && (malformedInputAction != null || unmappableInputAction != null)) { - charsetCopy = StandardCharsets.US_ASCII; + charsetCopy = DEFAULT_CHARSET; } return new CharCodingConfig(charsetCopy, malformedInputAction, unmappableInputAction); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/config/Http1Config.java b/httpcore5/src/main/java/org/apache/hc/core5/http/config/Http1Config.java index 54e26e814a..015162b371 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/config/Http1Config.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/config/Http1Config.java @@ -29,6 +29,7 @@ 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; @@ -47,6 +48,7 @@ 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; @@ -55,10 +57,11 @@ public class Http1Config { private final int maxEmptyLineCount; private final int initialWindowSize; - Http1Config(final int bufferSize, final int chunkSizeHint, final Timeout waitForContinueTimeout, - final int maxLineLength, final int maxHeaderCount, final int maxEmptyLineCount, - 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; @@ -68,6 +71,16 @@ public class Http1Config { 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; } @@ -99,7 +112,8 @@ public int getInitialWindowSize() { @Override public String toString() { final StringBuilder builder = new StringBuilder(); - builder.append("[bufferSize=").append(bufferSize) + builder.append("[version=").append(version) + .append(", bufferSize=").append(bufferSize) .append(", chunkSizeHint=").append(chunkSizeHint) .append(", waitForContinueTimeout=").append(waitForContinueTimeout) .append(", maxLineLength=").append(maxLineLength) @@ -116,6 +130,7 @@ public static Http1Config.Builder custom() { 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()) @@ -135,6 +150,7 @@ public static Http1Config.Builder copy(final Http1Config config) { public static class Builder { + private HttpVersion version; private int bufferSize; private int chunkSizeHint; private Timeout waitForContinueTimeout; @@ -144,6 +160,7 @@ public static class Builder { 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; @@ -153,6 +170,22 @@ public static class Builder { 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; @@ -191,6 +224,7 @@ public Builder setInitialWindowSize(final int initialWindowSize) { public Http1Config build() { return new Http1Config( + version, bufferSize, chunkSizeHint, waitForContinueTimeout, diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/config/Lookup.java b/httpcore5/src/main/java/org/apache/hc/core5/http/config/Lookup.java index ca80d75c55..28fc9c0427 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/config/Lookup.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/config/Lookup.java @@ -29,12 +29,19 @@ /** - * Generic lookup by low-case string ID. + * 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); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/config/Registry.java b/httpcore5/src/main/java/org/apache/hc/core5/http/config/Registry.java index c9d9ed81c4..ecd02092ea 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/config/Registry.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/config/Registry.java @@ -37,6 +37,7 @@ /** * Generic registry of items keyed by low-case string ID. * + * @param the type of values to lookup. * @since 4.3 */ @Contract(threading = ThreadingBehavior.SAFE) diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/config/RegistryBuilder.java b/httpcore5/src/main/java/org/apache/hc/core5/http/config/RegistryBuilder.java index 3f804b5221..ee65c44911 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/config/RegistryBuilder.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/config/RegistryBuilder.java @@ -36,12 +36,19 @@ /** * 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<>(); } @@ -51,6 +58,13 @@ public static RegistryBuilder create() { 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"); @@ -58,6 +72,11 @@ public RegistryBuilder register(final String id, final I 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); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/BasicEntityDetails.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/BasicEntityDetails.java index 597449ea0e..8f20abce26 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/BasicEntityDetails.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/BasicEntityDetails.java @@ -27,6 +27,8 @@ 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; @@ -54,7 +56,7 @@ public long getContentLength() { @Override public String getContentType() { - return contentType != null ? contentType.toString() : null; + return Objects.toString(contentType, null); } @Override @@ -69,7 +71,7 @@ public boolean isChunked() { @Override public Set getTrailerNames() { - return null; + return Collections.emptySet(); } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/DefaultAddressResolver.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/DefaultAddressResolver.java index 50a11cf483..ebd41249de 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/DefaultAddressResolver.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/DefaultAddressResolver.java @@ -40,6 +40,9 @@ */ public final class DefaultAddressResolver implements Resolver { + /** + * The singleton instance. + */ public static final DefaultAddressResolver INSTANCE = new DefaultAddressResolver(); @Override @@ -56,6 +59,9 @@ public InetSocketAddress resolve(final HttpHost host) { port = 443; } } + if (host.getAddress() != null) { + return new InetSocketAddress(host.getAddress(), port); + } return new InetSocketAddress(host.getHostName(), port); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/DefaultConnectionReuseStrategy.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/DefaultConnectionReuseStrategy.java index 10ca317272..c7638ac55d 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/DefaultConnectionReuseStrategy.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/DefaultConnectionReuseStrategy.java @@ -81,15 +81,34 @@ public boolean keepAlive( Args.notNull(response, "HTTP response"); if (request != null) { - final Iterator ti = new BasicTokenIterator(request.headerIterator(HttpHeaders.CONNECTION)); - while (ti.hasNext()) { - final String token = ti.next(); + // 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. @@ -110,21 +129,6 @@ public boolean keepAlive( } } - // Check for a self-terminating entity. If the end of the entity will - // be indicated by closing the connection, there is no keep-alive. - final Header teh = response.getFirstHeader(HttpHeaders.TRANSFER_ENCODING); - if (teh != null) { - if (!HeaderElements.CHUNKED_ENCODING.equalsIgnoreCase(teh.getValue())) { - return false; - } - } else { - final String method = request != null ? request.getMethod() : null; - if (MessageSupport.canResponseHaveBody(method, response) - && response.countHeaders(HttpHeaders.CONTENT_LENGTH) != 1) { - 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. @@ -133,7 +137,6 @@ public boolean keepAlive( headerIterator = response.headerIterator("Proxy-Connection"); } - final ProtocolVersion ver = response.getVersion() != null ? response.getVersion() : context.getProtocolVersion(); if (headerIterator.hasNext()) { if (ver.greaterEquals(HttpVersion.HTTP_1_1)) { final Iterator it = new BasicTokenIterator(headerIterator); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/DefaultContentLengthStrategy.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/DefaultContentLengthStrategy.java index 7f4cd80ec1..e0de94e204 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/DefaultContentLengthStrategy.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/DefaultContentLengthStrategy.java @@ -27,6 +27,8 @@ 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; @@ -37,7 +39,9 @@ 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 @@ -61,19 +65,29 @@ public class DefaultContentLengthStrategy implements ContentLengthStrategy { public DefaultContentLengthStrategy() { } + enum Coding { UNKNOWN, CHUNK } + @Override public long determineLength(final HttpMessage message) throws HttpException { Args.notNull(message, "HTTP message"); - // Although Transfer-Encoding is specified as a list, in practice - // it is either missing or has the single value "chunked". So we - // treat it as a single-valued header here. - final Header transferEncodingHeader = message.getFirstHeader(HttpHeaders.TRANSFER_ENCODING); - if (transferEncodingHeader != null) { - final String headerValue = transferEncodingHeader.getValue(); - if (HeaderElements.CHUNKED_ENCODING.equalsIgnoreCase(headerValue)) { + 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: " + headerValue); + throw new NotImplementedException("Unsupported transfer encoding: " + teh.getValue()); } if (message.countHeaders(HttpHeaders.CONTENT_LENGTH) > 1) { throw new ProtocolException("Multiple Content-Length headers"); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/EnglishReasonPhraseCatalog.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/EnglishReasonPhraseCatalog.java index 597c9a3060..7faf37e80d 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/EnglishReasonPhraseCatalog.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/EnglishReasonPhraseCatalog.java @@ -115,7 +115,7 @@ private static void setReason(final int status, final String reason) { /** Set up status code to "reason phrase" map. */ static { - // HTTP 1.1 Server status codes -- see RFC 7231 + // HTTP 1.1 Server status codes -- see RFC 9110 setReason(HttpStatus.SC_OK, "OK"); setReason(HttpStatus.SC_CREATED, @@ -210,8 +210,8 @@ private static void setReason(final int status, final String reason) { "Already Reported"); setReason(HttpStatus.SC_IM_USED, "IM Used"); - setReason(HttpStatus.SC_UNPROCESSABLE_ENTITY, - "Unprocessable Entity"); + setReason(HttpStatus.SC_UNPROCESSABLE_CONTENT, + "Unprocessable Content"); setReason(HttpStatus.SC_INSUFFICIENT_SPACE_ON_RESOURCE, "Insufficient Space On Resource"); setReason(HttpStatus.SC_METHOD_FAILURE, diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/HttpProcessors.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/HttpProcessors.java index 142a33c0d3..3da9d11dd7 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/HttpProcessors.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/HttpProcessors.java @@ -28,12 +28,14 @@ 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.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; @@ -60,13 +62,15 @@ public final class HttpProcessors { public static HttpProcessorBuilder customServer(final String serverInfo) { return HttpProcessorBuilder.create() .addAll( - new ResponseDate(), + ResponseConformance.INSTANCE, + ResponseDate.INSTANCE, new ResponseServer(!TextUtils.isBlank(serverInfo) ? serverInfo : VersionInfo.getSoftwareInfo(SOFTWARE, "org.apache.hc.core5", HttpProcessors.class)), - new ResponseContent(), - new ResponseConnControl()) + ResponseContent.INSTANCE, + ResponseConnControl.INSTANCE) .addAll( - new RequestValidateHost()); + RequestValidateHost.INSTANCE, + RequestConformance.INSTANCE); } /** @@ -100,8 +104,8 @@ public static HttpProcessor server() { public static HttpProcessorBuilder customClient(final String agentInfo) { return HttpProcessorBuilder.create() .addAll( - RequestContent.INSTANCE, RequestTargetHost.INSTANCE, + RequestContent.INSTANCE, RequestConnControl.INSTANCE, new RequestUserAgent(!TextUtils.isBlank(agentInfo) ? agentInfo : VersionInfo.getSoftwareInfo(SOFTWARE, "org.apache.hc.core5", HttpProcessors.class)), diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/ServerSupport.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/ServerSupport.java index 85b1ee0582..137923e4d8 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/ServerSupport.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/ServerSupport.java @@ -27,9 +27,6 @@ package org.apache.hc.core5.http.impl; import org.apache.hc.core5.annotation.Internal; -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.HttpStatus; import org.apache.hc.core5.http.MethodNotSupportedException; import org.apache.hc.core5.http.MisdirectedRequestException; @@ -46,19 +43,6 @@ @Internal public class ServerSupport { - public static void validateResponse( - final HttpResponse response, - final EntityDetails responseEntityDetails) throws HttpException { - final int status = response.getCode(); - switch (status) { - case HttpStatus.SC_NO_CONTENT: - case HttpStatus.SC_NOT_MODIFIED: - if (responseEntityDetails != null) { - throw new HttpException("Response " + status + " must not enclose an entity"); - } - } - } - public static String toErrorMessage(final Exception ex) { final String message = ex.getMessage(); return message != null ? message : ex.toString(); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequester.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequester.java index 7bfa0a4679..2e91765c7e 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequester.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequester.java @@ -38,8 +38,11 @@ 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; @@ -127,4 +130,14 @@ 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); + } + } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequesterBootstrap.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequesterBootstrap.java index 879adea10c..12611bf5ab 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequesterBootstrap.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequesterBootstrap.java @@ -87,6 +87,8 @@ public static AsyncRequesterBootstrap bootstrap() { /** * Sets I/O reactor configuration. + * + * @return this instance. */ public final AsyncRequesterBootstrap setIOReactorConfig(final IOReactorConfig ioReactorConfig) { this.ioReactorConfig = ioReactorConfig; @@ -95,6 +97,8 @@ public final AsyncRequesterBootstrap setIOReactorConfig(final IOReactorConfig io /** * Sets HTTP/1.1 protocol parameters + * + * @return this instance. */ public final AsyncRequesterBootstrap setHttp1Config(final Http1Config http1Config) { this.http1Config = http1Config; @@ -103,6 +107,8 @@ public final AsyncRequesterBootstrap setHttp1Config(final Http1Config http1Confi /** * Sets message char coding. + * + * @return this instance. */ public final AsyncRequesterBootstrap setCharCodingConfig(final CharCodingConfig charCodingConfig) { this.charCodingConfig = charCodingConfig; @@ -110,7 +116,9 @@ public final AsyncRequesterBootstrap setCharCodingConfig(final CharCodingConfig } /** - * Assigns {@link HttpProcessor} instance. + * Sets {@link HttpProcessor} instance. + * + * @return this instance. */ public final AsyncRequesterBootstrap setHttpProcessor(final HttpProcessor httpProcessor) { this.httpProcessor = httpProcessor; @@ -118,7 +126,9 @@ public final AsyncRequesterBootstrap setHttpProcessor(final HttpProcessor httpPr } /** - * Assigns {@link ConnectionReuseStrategy} instance. + * Sets {@link ConnectionReuseStrategy} instance. + * + * @return this instance. */ public final AsyncRequesterBootstrap setConnectionReuseStrategy(final ConnectionReuseStrategy connStrategy) { this.connStrategy = connStrategy; @@ -141,7 +151,9 @@ public final AsyncRequesterBootstrap setTimeToLive(final Timeout timeToLive) { } /** - * Assigns {@link PoolReusePolicy} instance. + * Sets {@link PoolReusePolicy} instance. + * + * @return this instance. */ public final AsyncRequesterBootstrap setPoolReusePolicy(final PoolReusePolicy poolReusePolicy) { this.poolReusePolicy = poolReusePolicy; @@ -149,7 +161,9 @@ public final AsyncRequesterBootstrap setPoolReusePolicy(final PoolReusePolicy po } /** - * Assigns {@link PoolConcurrencyPolicy} instance. + * Sets {@link PoolConcurrencyPolicy} instance. + * + * @return this instance. */ @Experimental public final AsyncRequesterBootstrap setPoolConcurrencyPolicy(final PoolConcurrencyPolicy poolConcurrencyPolicy) { @@ -158,7 +172,9 @@ public final AsyncRequesterBootstrap setPoolConcurrencyPolicy(final PoolConcurre } /** - * Assigns {@link TlsStrategy} instance. + * Sets {@link TlsStrategy} instance. + * + * @return this instance. */ public final AsyncRequesterBootstrap setTlsStrategy(final TlsStrategy tlsStrategy) { this.tlsStrategy = tlsStrategy; @@ -171,7 +187,9 @@ public final AsyncRequesterBootstrap setTlsHandshakeTimeout(final Timeout handsh } /** - * Assigns {@link IOSession} {@link Decorator} instance. + * Sets {@link IOSession} {@link Decorator} instance. + * + * @return this instance. */ public final AsyncRequesterBootstrap setIOSessionDecorator(final Decorator ioSessionDecorator) { this.ioSessionDecorator = ioSessionDecorator; @@ -179,7 +197,9 @@ public final AsyncRequesterBootstrap setIOSessionDecorator(final Decorator exceptionCallback) { this.exceptionCallback = exceptionCallback; @@ -187,7 +207,9 @@ public final AsyncRequesterBootstrap setExceptionCallback(final Callback connPoolListener) { this.connPoolListener = connPoolListener; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServerBootstrap.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServerBootstrap.java index dfbe1fabe3..a47d041112 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServerBootstrap.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServerBootstrap.java @@ -33,6 +33,7 @@ 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; @@ -44,6 +45,7 @@ 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; @@ -58,9 +60,9 @@ import org.apache.hc.core5.http.nio.support.TerminalAsyncServerFilter; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http.protocol.LookupRegistry; -import org.apache.hc.core5.http.protocol.RequestHandlerRegistry; 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.IOSession; @@ -73,12 +75,14 @@ * * @since 5.0 */ +@SuppressWarnings("deprecation") public class AsyncServerBootstrap { - private final List>> handlerList; + private final List>> routeEntries; private final List> filters; private String canonicalHostName; - private LookupRegistry> lookupRegistry; + private HttpRequestMapper> requestRouter; + private org.apache.hc.core5.http.protocol.LookupRegistry> lookupRegistry; private IOReactorConfig ioReactorConfig; private Http1Config http1Config; private CharCodingConfig charCodingConfig; @@ -92,16 +96,24 @@ public class AsyncServerBootstrap { private Http1StreamListener streamListener; private AsyncServerBootstrap() { - this.handlerList = new ArrayList<>(); + 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; @@ -110,6 +122,9 @@ public final AsyncServerBootstrap setCanonicalHostName(final String canonicalHos /** * Sets I/O reactor configuration. + * + * @param ioReactorConfig I/O reactor configuration. + * @return this instance. */ public final AsyncServerBootstrap setIOReactorConfig(final IOReactorConfig ioReactorConfig) { this.ioReactorConfig = ioReactorConfig; @@ -118,6 +133,9 @@ public final AsyncServerBootstrap setIOReactorConfig(final IOReactorConfig ioRea /** * 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; @@ -125,7 +143,10 @@ public final AsyncServerBootstrap setHttp1Config(final Http1Config http1Config) } /** - * Sets connection configuration. + * Sets char coding configuration. + * + * @param charCodingConfig char coding configuration. + * @return this instance. */ public final AsyncServerBootstrap setCharCodingConfig(final CharCodingConfig charCodingConfig) { this.charCodingConfig = charCodingConfig; @@ -133,7 +154,10 @@ public final AsyncServerBootstrap setCharCodingConfig(final CharCodingConfig cha } /** - * Assigns {@link org.apache.hc.core5.http.protocol.HttpProcessor} instance. + * Sets {@link HttpProcessor} instance. + * + * @param httpProcessor {@link HttpProcessor} instance. + * @return this instance. */ public final AsyncServerBootstrap setHttpProcessor(final HttpProcessor httpProcessor) { this.httpProcessor = httpProcessor; @@ -141,7 +165,10 @@ public final AsyncServerBootstrap setHttpProcessor(final HttpProcessor httpProce } /** - * Assigns {@link org.apache.hc.core5.http.ConnectionReuseStrategy} instance. + * Sets {@link ConnectionReuseStrategy} instance. + * + * @param connStrategy {@link ConnectionReuseStrategy} instance. + * @return this instance. */ public final AsyncServerBootstrap setConnectionReuseStrategy(final ConnectionReuseStrategy connStrategy) { this.connStrategy = connStrategy; @@ -149,7 +176,10 @@ public final AsyncServerBootstrap setConnectionReuseStrategy(final ConnectionReu } /** - * Assigns {@link TlsStrategy} instance. + * Sets {@link TlsStrategy} instance. + * + * @param tlsStrategy {@link TlsStrategy} instance. + * @return this instance. */ public final AsyncServerBootstrap setTlsStrategy(final TlsStrategy tlsStrategy) { this.tlsStrategy = tlsStrategy; @@ -157,7 +187,10 @@ public final AsyncServerBootstrap setTlsStrategy(final TlsStrategy tlsStrategy) } /** - * Assigns TLS handshake {@link Timeout}. + * Sets TLS handshake {@link Timeout}. + * + * @param handshakeTimeout TLS handshake {@link Timeout}. + * @return this instance. */ public final AsyncServerBootstrap setTlsHandshakeTimeout(final Timeout handshakeTimeout) { this.handshakeTimeout = handshakeTimeout; @@ -165,7 +198,10 @@ public final AsyncServerBootstrap setTlsHandshakeTimeout(final Timeout handshake } /** - * Assigns {@link IOSession} {@link Decorator} instance. + * 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; @@ -173,7 +209,10 @@ public final AsyncServerBootstrap setIOSessionDecorator(final Decorator exceptionCallback) { this.exceptionCallback = exceptionCallback; @@ -181,7 +220,10 @@ public final AsyncServerBootstrap setExceptionCallback(final Callback } /** - * Assigns {@link IOSessionListener} instance. + * Sets {@link IOSessionListener} instance. + * + * @param sessionListener {@link IOSessionListener} instance. + * @return this instance. */ public final AsyncServerBootstrap setIOSessionListener(final IOSessionListener sessionListener) { this.sessionListener = sessionListener; @@ -189,16 +231,36 @@ public final AsyncServerBootstrap setIOSessionListener(final IOSessionListener s } /** - * Assigns {@link LookupRegistry} instance. + * 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 LookupRegistry> lookupRegistry) { this.lookupRegistry = lookupRegistry; return this; } /** - * Assigns {@link Http1StreamListener} instance. + * 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) { @@ -210,38 +272,58 @@ public final AsyncServerBootstrap setStreamListener(final Http1StreamListener st * 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. + * @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, "Supplier"); - handlerList.add(new HandlerEntry<>(null, uriPattern, supplier)); + 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. + * 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. + * @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 registerVirtual(final String hostname, final String uriPattern, final Supplier supplier) { + 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, "Supplier"); - handlerList.add(new HandlerEntry<>(hostname, uriPattern, supplier)); + 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, @@ -252,22 +334,37 @@ public final AsyncServerBootstrap register( /** * Registers the given {@link AsyncServerRequestHandler} as a handler for URIs - * matching the given host and the pattern. + * 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) { - registerVirtual(hostname, uriPattern, () -> new BasicServerExchangeHandler<>(requestHandler)); - return this; + 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"); @@ -279,6 +376,8 @@ public final AsyncServerBootstrap addFilterBefore(final String existing, final S /** * 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"); @@ -289,7 +388,9 @@ public final AsyncServerBootstrap addFilterAfter(final String existing, final St } /** - * Replace an existing filter with the given name with new filter. + * 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"); @@ -299,7 +400,9 @@ public final AsyncServerBootstrap replaceFilter(final String existing, final Asy } /** - * Add an filter to the head of the processing list. + * 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"); @@ -309,7 +412,9 @@ public final AsyncServerBootstrap addFilterFirst(final String name, final AsyncF } /** - * Add an filter to the tail of the processing list. + * 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"); @@ -319,19 +424,32 @@ public final AsyncServerBootstrap addFilterLast(final String name, final AsyncFi } public HttpAsyncServer create() { - final RequestHandlerRegistry> registry = new RequestHandlerRegistry<>( - canonicalHostName != null ? canonicalHostName : InetAddressUtils.getCanonicalLocalHostName(), - () -> lookupRegistry != null ? lookupRegistry : - UriPatternType.newMatcher(UriPatternType.URI_PATTERN)); - for (final HandlerEntry> entry: handlerList) { - registry.register(entry.hostname, entry.uriPattern, entry.handler); + 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(registry)), + new TerminalAsyncServerFilter(new DefaultAsyncResponseExchangeHandlerFactory(requestRouterCopy)), StandardFilter.MAIN_HANDLER.name()); filterChainDefinition.addFirst( new AsyncServerExpectationFilter(), @@ -368,17 +486,17 @@ public HttpAsyncServer create() { handlerFactory = new AsyncServerFilterChainExchangeHandlerFactory(execChain, exceptionCallback); } else { - handlerFactory = new DefaultAsyncResponseExchangeHandlerFactory(registry, handler -> new BasicAsyncServerExpectationDecorator(handler, exceptionCallback)); + handlerFactory = new DefaultAsyncResponseExchangeHandlerFactory(requestRouterCopy, handler -> new BasicAsyncServerExpectationDecorator(handler, exceptionCallback)); } final ServerHttp1StreamDuplexerFactory streamHandlerFactory = new ServerHttp1StreamDuplexerFactory( httpProcessor != null ? httpProcessor : HttpProcessors.server(), handlerFactory, - http1Config != null ? http1Config : Http1Config.DEFAULT, + http1Config, charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT, connStrategy != null ? connStrategy : DefaultConnectionReuseStrategy.INSTANCE, - DefaultHttpRequestParserFactory.INSTANCE, - DefaultHttpResponseWriterFactory.INSTANCE, + new DefaultHttpRequestParserFactory(http1Config), + new DefaultHttpResponseWriterFactory(http1Config), DefaultContentLengthStrategy.INSTANCE, DefaultContentLengthStrategy.INSTANCE, streamListener); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpAsyncRequester.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpAsyncRequester.java index c87b2d407b..bb2cbbeaf7 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpAsyncRequester.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpAsyncRequester.java @@ -49,7 +49,6 @@ 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.ProtocolException; import org.apache.hc.core5.http.impl.DefaultAddressResolver; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; @@ -274,7 +273,11 @@ public Future connect(final HttpHost host, final Timeout ti return connect(host, timeout, null, null); } + /** + * @since 5.3 + */ public void execute( + final HttpHost target, final AsyncClientExchangeHandler exchangeHandler, final HandlerFactory pushHandlerFactory, final Timeout timeout, @@ -284,13 +287,11 @@ public void execute( Args.notNull(executeContext, "Context"); try { exchangeHandler.produceRequest((request, entityDetails, requestContext) -> { - final String scheme = request.getScheme(); - final URIAuthority authority = request.getAuthority(); - if (authority == null) { - throw new ProtocolException("Request authority not specified"); + final HttpHost host = target != null ? target : defaultTarget(request); + if (request.getAuthority() == null) { + request.setAuthority(new URIAuthority(host.getHostName(), host.getPort())); } - final HttpHost target = new HttpHost(scheme, authority); - connect(target, timeout, null, new FutureCallback() { + connect(host, timeout, null, new FutureCallback() { @Override public void completed(final AsyncClientEndpoint endpoint) { @@ -382,6 +383,14 @@ public void cancelled() { } } + 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, @@ -389,7 +398,12 @@ public void execute( 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, @@ -411,10 +425,20 @@ public void completed(final T result) { } }); - execute(exchangeHandler, pushHandlerFactory, timeout, context != null ? context : HttpCoreContext.create()); + 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, @@ -424,6 +448,19 @@ public final Future execute( 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, diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpRequester.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpRequester.java index 957509cfaa..bee1448e6e 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpRequester.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpRequester.java @@ -33,9 +33,6 @@ import java.net.InetSocketAddress; import java.net.Proxy; import java.net.Socket; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -79,13 +76,13 @@ 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.io.SocketSupport; 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.Asserts; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; @@ -242,13 +239,7 @@ public T execute( } } - private Socket createSocket(final HttpHost targetHost) throws IOException { - final Socket sock; - if (socketConfig.getSocksProxyAddress() != null) { - sock = new Socket(new Proxy(Proxy.Type.SOCKS, socketConfig.getSocksProxyAddress())); - } else { - sock = new Socket(); - } + private HttpClientConnection createConnection(final Socket sock, final HttpHost targetHost) throws IOException { sock.setSoTimeout(socketConfig.getSoTimeout().toMillisecondsIntBound()); sock.setReuseAddress(socketConfig.isSoReuseAddress()); sock.setTcpNoDelay(socketConfig.isTcpNoDelay()); @@ -259,28 +250,25 @@ private Socket createSocket(final HttpHost targetHost) throws IOException { if (socketConfig.getSndBufSize() > 0) { sock.setSendBufferSize(socketConfig.getSndBufSize()); } + if (this.socketConfig.getTcpKeepIdle() > 0) { + SocketSupport.setOption(sock, SocketSupport.TCP_KEEPIDLE, this.socketConfig.getTcpKeepIdle()); + } + if (this.socketConfig.getTcpKeepInterval() > 0) { + SocketSupport.setOption(sock, SocketSupport.TCP_KEEPINTERVAL, this.socketConfig.getTcpKeepInterval()); + } + if (this.socketConfig.getTcpKeepCount() > 0) { + SocketSupport.setOption(sock, SocketSupport.TCP_KEEPCOUNT, this.socketConfig.getTcpKeepCount()); + } final int linger = socketConfig.getSoLinger().toMillisecondsIntBound(); if (linger >= 0) { sock.setSoLinger(true, linger); } final InetSocketAddress targetAddress = addressResolver.resolve(targetHost); - // Run this under a doPrivileged to support lib users that run under a SecurityManager this allows granting connect permissions - // only to this library - try { - AccessController.doPrivileged((PrivilegedExceptionAction) () -> { - sock.connect(targetAddress, socketConfig.getSoTimeout().toMillisecondsIntBound()); - return null; - }); - } catch (final PrivilegedActionException e) { - Asserts.check(e.getCause() instanceof IOException, - "method contract violation only checked exceptions are wrapped: " + e.getCause()); - // only checked exceptions are wrapped - error and RTExceptions are rethrown by doPrivileged - throw (IOException) e.getCause(); - } + sock.connect(targetAddress, socketConfig.getSoTimeout().toMillisecondsIntBound()); if (URIScheme.HTTPS.same(targetHost.getSchemeName())) { final SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket( - sock, targetHost.getHostName(), targetAddress.getPort(), true); + sock, targetHost.getHostName(), targetAddress.getPort(), false); if (this.sslSetupHandler != null) { final SSLParameters sslParameters = sslSocket.getSSLParameters(); this.sslSetupHandler.execute(sslParameters); @@ -295,13 +283,13 @@ private Socket createSocket(final HttpHost targetHost) throws IOException { if (sslSessionVerifier != null) { sslSessionVerifier.verify(targetHost, session); } + return connectFactory.createConnection(sslSocket, sock); } catch (final IOException ex) { Closer.closeQuietly(sslSocket); throw ex; } - return sslSocket; } - return sock; + return connectFactory.createConnection(sock); } public ClassicHttpResponse execute( @@ -314,7 +302,7 @@ public ClassicHttpResponse execute( Args.notNull(request, "HTTP request"); final Future> leaseFuture = connPool.lease(targetHost, null, connectTimeout, null); final PoolEntry poolEntry; - final Timeout timeout = Timeout.defaultsToDisabled(connectTimeout); + final Timeout timeout = Timeout.defaultsToInfinite(connectTimeout); try { poolEntry = leaseFuture.get(timeout.getDuration(), timeout.getTimeUnit()); } catch (final InterruptedException ex) { @@ -329,9 +317,19 @@ public ClassicHttpResponse execute( try { HttpClientConnection connection = poolEntry.getConnection(); if (connection == null) { - final Socket socket = createSocket(targetHost); - connection = connectFactory.createConnection(socket); - poolEntry.assignConnection(connection); + 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.getHostName(), targetHost.getPort())); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpServer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpServer.java index 410d6bed2f..5a8deaa2d7 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpServer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpServer.java @@ -36,6 +36,7 @@ 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; @@ -55,6 +56,7 @@ 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.io.SocketSupport; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; @@ -74,6 +76,7 @@ enum Status { READY, ACTIVE, STOPPING } 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; @@ -92,6 +95,7 @@ public HttpServer( 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"); @@ -103,6 +107,7 @@ public HttpServer( 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( @@ -141,6 +146,15 @@ public void start() throws IOException { if (this.socketConfig.getRcvBufSize() > 0) { this.serverSocket.setReceiveBufferSize(this.socketConfig.getRcvBufSize()); } + if (this.socketConfig.getTcpKeepIdle() > 0) { + SocketSupport.setOption(this.serverSocket, SocketSupport.TCP_KEEPIDLE, this.socketConfig.getTcpKeepIdle()); + } + if (this.socketConfig.getTcpKeepInterval() > 0) { + SocketSupport.setOption(this.serverSocket, SocketSupport.TCP_KEEPINTERVAL, this.socketConfig.getTcpKeepInterval()); + } + if (this.socketConfig.getTcpKeepCount() > 0) { + SocketSupport.setOption(this.serverSocket, SocketSupport.TCP_KEEPCOUNT, this.socketConfig.getTcpKeepCount()); + } if (this.sslSetupHandler != null && this.serverSocket instanceof SSLServerSocket) { final SSLServerSocket sslServerSocket = (SSLServerSocket) this.serverSocket; final SSLParameters sslParameters = sslServerSocket.getSSLParameters(); @@ -152,6 +166,8 @@ public void start() throws IOException { this.serverSocket, this.httpService, this.connectionFactory, + this.sslContext != null ? this.sslContext.getSocketFactory() : null, + this.sslSetupHandler, this.exceptionListener, this.workerExecutorService); this.listenerExecutorService.execute(this.requestListener); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/RequestListener.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/RequestListener.java index c09ce11c07..1c58748a94 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/RequestListener.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/RequestListener.java @@ -32,11 +32,20 @@ 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 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.io.SocketSupport; class RequestListener implements Runnable { @@ -44,6 +53,8 @@ class RequestListener implements Runnable { 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; @@ -53,15 +64,64 @@ public RequestListener( 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.connectionFactory = connectionFactory; this.httpService = httpService; + this.connectionFactory = connectionFactory; + this.sslSocketFactory = sslSocketFactory; + this.sslSetupHandler = sslSetupHandler; this.exceptionListener = exceptionListener; this.executorService = executorService; - this.terminated = new AtomicBoolean(false); + this.terminated = new AtomicBoolean(); + } + + 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 (this.socketConfig.getTcpKeepIdle() > 0) { + SocketSupport.setOption(this.serverSocket, SocketSupport.TCP_KEEPIDLE, this.socketConfig.getTcpKeepIdle()); + } + if (this.socketConfig.getTcpKeepInterval() > 0) { + SocketSupport.setOption(this.serverSocket, SocketSupport.TCP_KEEPINTERVAL, this.socketConfig.getTcpKeepInterval()); + } + if (this.socketConfig.getTcpKeepCount() > 0) { + SocketSupport.setOption(this.serverSocket, SocketSupport.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 @@ -69,21 +129,14 @@ public void run() { try { while (!isTerminated() && !Thread.interrupted()) { final Socket socket = this.serverSocket.accept(); - 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()); + 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); + throw ex; } - final HttpServerConnection conn = this.connectionFactory.createConnection(socket); - final Worker worker = new Worker(this.httpService, conn, this.exceptionListener); - this.executorService.execute(worker); } } catch (final Exception ex) { this.exceptionListener.onError(ex); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/RequesterBootstrap.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/RequesterBootstrap.java index 15791af2dc..bd3dfd3550 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/RequesterBootstrap.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/RequesterBootstrap.java @@ -65,6 +65,7 @@ public class RequesterBootstrap { private HttpProcessor httpProcessor; + private Http1Config http1Config; private ConnectionReuseStrategy connReuseStrategy; private SocketConfig socketConfig; private HttpConnectionFactory connectFactory; @@ -87,7 +88,9 @@ public static RequesterBootstrap bootstrap() { } /** - * Assigns {@link HttpProcessor} instance. + * Sets {@link HttpProcessor} instance. + * + * @return this instance. */ public final RequesterBootstrap setHttpProcessor(final HttpProcessor httpProcessor) { this.httpProcessor = httpProcessor; @@ -95,7 +98,19 @@ public final RequesterBootstrap setHttpProcessor(final HttpProcessor httpProcess } /** - * Assigns {@link ConnectionReuseStrategy} instance. + * 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; @@ -104,6 +119,8 @@ public final RequesterBootstrap setConnectionReuseStrategy(final ConnectionReuse /** * Sets socket configuration. + * + * @return this instance. */ public final RequesterBootstrap setSocketConfig(final SocketConfig socketConfig) { this.socketConfig = socketConfig; @@ -126,7 +143,9 @@ public final RequesterBootstrap setSslSocketFactory(final SSLSocketFactory sslSo } /** - * Assigns {@link Callback} for {@link SSLParameters}. + * Sets {@link Callback} for {@link SSLParameters}. + * + * @return this instance. */ public final RequesterBootstrap setSslSetupHandler(final Callback sslSetupHandler) { this.sslSetupHandler = sslSetupHandler; @@ -134,7 +153,9 @@ public final RequesterBootstrap setSslSetupHandler(final Callback } /** - * Assigns {@link SSLSessionVerifier} instance. + * Sets {@link SSLSessionVerifier} instance. + * + * @return this instance. */ public final RequesterBootstrap setSslSessionVerifier(final SSLSessionVerifier sslSessionVerifier) { this.sslSessionVerifier = sslSessionVerifier; @@ -179,7 +200,7 @@ public final RequesterBootstrap setConnPoolListener(final ConnPoolListener connPool; @@ -209,9 +230,9 @@ public HttpRequester create() { connPool, socketConfig != null ? socketConfig : SocketConfig.DEFAULT, connectFactory != null ? connectFactory : new DefaultBHttpClientConnectionFactory( - Http1Config.DEFAULT, CharCodingConfig.DEFAULT), + http1Config, CharCodingConfig.DEFAULT), sslSocketFactory, - sslSetupHandler != null ? sslSetupHandler : new DefaultTlsSetupHandler(), + sslSetupHandler != null ? sslSetupHandler : DefaultTlsSetupHandler.CLIENT, sslSessionVerifier, DefaultAddressResolver.INSTANCE); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/ServerBootstrap.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/ServerBootstrap.java index d2b16d8477..da887dbf1c 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/ServerBootstrap.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/ServerBootstrap.java @@ -39,6 +39,7 @@ 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; @@ -51,6 +52,7 @@ 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; @@ -64,10 +66,9 @@ 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.LookupRegistry; -import org.apache.hc.core5.http.protocol.RequestHandlerRegistry; 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; /** @@ -75,12 +76,14 @@ * * @since 4.4 */ +@SuppressWarnings("deprecation") public class ServerBootstrap { - private final List> handlerList; + private final List> routeEntries; private final List> filters; private String canonicalHostName; - private LookupRegistry lookupRegistry; + private HttpRequestMapper requestRouter; + private org.apache.hc.core5.http.protocol.LookupRegistry lookupRegistry; private int listenerPort; private InetAddress localAddress; private SocketConfig socketConfig; @@ -97,7 +100,7 @@ public class ServerBootstrap { private Http1StreamListener streamListener; private ServerBootstrap() { - this.handlerList = new ArrayList<>(); + this.routeEntries = new ArrayList<>(); this.filters = new ArrayList<>(); } @@ -124,7 +127,7 @@ public final ServerBootstrap setListenerPort(final int listenerPort) { } /** - * Assigns local interface for the listener. + * Sets local interface for the listener. */ public final ServerBootstrap setLocalAddress(final InetAddress localAddress) { this.localAddress = localAddress; @@ -140,7 +143,7 @@ public final ServerBootstrap setSocketConfig(final SocketConfig socketConfig) { } /** - * Sets connection configuration. + * Sets HTTP/1 protocol configuration. */ public final ServerBootstrap setHttp1Config(final Http1Config http1Config) { this.http1Config = http1Config; @@ -156,7 +159,7 @@ public final ServerBootstrap setCharCodingConfig(final CharCodingConfig charCodi } /** - * Assigns {@link HttpProcessor} instance. + * Sets {@link HttpProcessor} instance. */ public final ServerBootstrap setHttpProcessor(final HttpProcessor httpProcessor) { this.httpProcessor = httpProcessor; @@ -164,7 +167,7 @@ public final ServerBootstrap setHttpProcessor(final HttpProcessor httpProcessor) } /** - * Assigns {@link ConnectionReuseStrategy} instance. + * Sets {@link ConnectionReuseStrategy} instance. */ public final ServerBootstrap setConnectionReuseStrategy(final ConnectionReuseStrategy connStrategy) { this.connStrategy = connStrategy; @@ -172,7 +175,7 @@ public final ServerBootstrap setConnectionReuseStrategy(final ConnectionReuseStr } /** - * Assigns {@link HttpResponseFactory} instance. + * Sets {@link HttpResponseFactory} instance. */ public final ServerBootstrap setResponseFactory(final HttpResponseFactory responseFactory) { this.responseFactory = responseFactory; @@ -180,9 +183,10 @@ public final ServerBootstrap setResponseFactory(final HttpResponseFactory lookupRegistry) { + @Deprecated + public final ServerBootstrap setLookupRegistry(final org.apache.hc.core5.http.protocol.LookupRegistry lookupRegistry) { this.lookupRegistry = lookupRegistry; return this; } @@ -197,7 +201,7 @@ public final ServerBootstrap setLookupRegistry(final LookupRegistry(null, uriPattern, requestHandler)); + routeEntries.add(new RequestRouter.Entry<>(uriPattern, requestHandler)); return this; } @@ -208,17 +212,38 @@ public final ServerBootstrap register(final String uriPattern, final HttpRequest * @param hostname * @param uriPattern the pattern to register the handler for. * @param requestHandler the handler. + * + * @since 5.3 */ - public final ServerBootstrap registerVirtual(final String hostname, final String uriPattern, final HttpRequestHandler requestHandler) { + 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, "Supplier"); - handlerList.add(new HandlerEntry<>(hostname, uriPattern, requestHandler)); + Args.notNull(requestHandler, "Request handler"); + routeEntries.add(new RequestRouter.Entry<>(hostname, uriPattern, requestHandler)); return this; } /** - * Assigns {@link HttpConnectionFactory} instance. + * @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) { @@ -227,7 +252,7 @@ public final ServerBootstrap setConnectionFactory( } /** - * Assigns {@link javax.net.ServerSocketFactory} instance. + * Sets {@link javax.net.ServerSocketFactory} instance. */ public final ServerBootstrap setServerSocketFactory(final ServerSocketFactory serverSocketFactory) { this.serverSocketFactory = serverSocketFactory; @@ -235,7 +260,7 @@ public final ServerBootstrap setServerSocketFactory(final ServerSocketFactory se } /** - * Assigns {@link javax.net.ssl.SSLContext} instance. + * Sets {@link javax.net.ssl.SSLContext} instance. *

* Please note this value can be overridden by the {@link #setServerSocketFactory( * javax.net.ServerSocketFactory)} method. @@ -246,7 +271,7 @@ public final ServerBootstrap setSslContext(final SSLContext sslContext) { } /** - * Assigns {@link Callback} for {@link SSLParameters}. + * Sets {@link Callback} for {@link SSLParameters}. */ public final ServerBootstrap setSslSetupHandler(final Callback sslSetupHandler) { this.sslSetupHandler = sslSetupHandler; @@ -254,7 +279,7 @@ public final ServerBootstrap setSslSetupHandler(final Callback ss } /** - * Assigns {@link ExceptionListener} instance. + * Sets {@link ExceptionListener} instance. */ public final ServerBootstrap setExceptionListener(final ExceptionListener exceptionListener) { this.exceptionListener = exceptionListener; @@ -262,7 +287,7 @@ public final ServerBootstrap setExceptionListener(final ExceptionListener except } /** - * Assigns {@link ExceptionListener} instance. + * Sets {@link ExceptionListener} instance. */ public final ServerBootstrap setStreamListener(final Http1StreamListener streamListener) { this.streamListener = streamListener; @@ -322,12 +347,27 @@ public final ServerBootstrap addFilterLast(final String name, final HttpFilterHa } public HttpServer create() { - final RequestHandlerRegistry handlerRegistry = new RequestHandlerRegistry<>( - canonicalHostName != null ? canonicalHostName : InetAddressUtils.getCanonicalLocalHostName(), - () -> lookupRegistry != null ? lookupRegistry : - UriPatternType.newMatcher(UriPatternType.URI_PATTERN)); - for (final HandlerEntry entry: handlerList) { - handlerRegistry.register(entry.hostname, entry.uriPattern, entry.handler); + 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 HttpServerRequestHandler requestHandler; @@ -335,7 +375,7 @@ public HttpServer create() { final NamedElementChain filterChainDefinition = new NamedElementChain<>(); filterChainDefinition.addLast( new TerminalServerFilter( - handlerRegistry, + requestRouterCopy, this.responseFactory != null ? this.responseFactory : DefaultClassicHttpResponseFactory.INSTANCE), StandardFilter.MAIN_HANDLER.name()); filterChainDefinition.addFirst( @@ -373,28 +413,21 @@ public HttpServer create() { requestHandler = new HttpServerFilterChainRequestHandler(filterChain); } else { requestHandler = new BasicHttpServerExpectationDecorator(new BasicHttpServerRequestHandler( - handlerRegistry, + 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); - ServerSocketFactory serverSocketFactoryCopy = this.serverSocketFactory; - if (serverSocketFactoryCopy == null) { - if (this.sslContext != null) { - serverSocketFactoryCopy = this.sslContext.getServerSocketFactory(); - } else { - serverSocketFactoryCopy = ServerSocketFactory.getDefault(); - } - } - HttpConnectionFactory connectionFactoryCopy = this.connectionFactory; if (connectionFactoryCopy == null) { - final String scheme = serverSocketFactoryCopy instanceof SSLServerSocketFactory ? URIScheme.HTTPS.id : URIScheme.HTTP.id; + final String scheme = serverSocketFactory instanceof SSLServerSocketFactory || sslContext != null ? + URIScheme.HTTPS.id : URIScheme.HTTP.id; connectionFactoryCopy = new DefaultBHttpServerConnectionFactory(scheme, this.http1Config, this.charCodingConfig); } @@ -403,9 +436,10 @@ public HttpServer create() { httpService, this.localAddress, this.socketConfig != null ? this.socketConfig : SocketConfig.DEFAULT, - serverSocketFactoryCopy, + serverSocketFactory, connectionFactoryCopy, - sslSetupHandler != null ? sslSetupHandler : new DefaultTlsSetupHandler(), + sslContext, + sslSetupHandler != null ? sslSetupHandler : DefaultTlsSetupHandler.SERVER, this.exceptionListener != null ? this.exceptionListener : ExceptionListener.NO_OP); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/Worker.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/Worker.java index 8d6a88382f..1e23fca3f7 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/Worker.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/Worker.java @@ -29,7 +29,6 @@ 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.BasicHttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.io.CloseMode; @@ -56,11 +55,9 @@ public HttpServerConnection getConnection() { @Override public void run() { try { - final BasicHttpContext localContext = new BasicHttpContext(); - final HttpCoreContext context = HttpCoreContext.adapt(localContext); while (!Thread.interrupted() && this.conn.isOpen()) { + final HttpCoreContext context = HttpCoreContext.create(); this.httpservice.handleRequest(this.conn, context); - localContext.clear(); } this.conn.close(); } catch (final Exception ex) { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/AbstractMessageParser.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/AbstractMessageParser.java index 43329b2ce6..cf5e40d48b 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/AbstractMessageParser.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/AbstractMessageParser.java @@ -29,6 +29,7 @@ import java.io.IOException; import java.io.InputStream; +import java.io.InterruptedIOException; import java.util.ArrayList; import java.util.List; @@ -49,6 +50,7 @@ * 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 { @@ -65,24 +67,25 @@ public abstract class AbstractMessageParser implements Ht private T message; /** - * Creates new instance of AbstractMessageParser. - * - * @param lineParser the line parser. If {@code null} - * {@link org.apache.hc.core5.http.message.LazyLineParser#INSTANCE} will be used. - * @param http1Config the message http1Config. If {@code null} - * {@link Http1Config#DEFAULT} will be used. - * - * @since 4.3 + * @since 5.3 */ - public AbstractMessageParser(final LineParser lineParser, final Http1Config http1Config) { + public AbstractMessageParser(final Http1Config http1Config, final LineParser lineParser) { super(); - this.lineParser = lineParser != null ? lineParser : LazyLineParser.INSTANCE; 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; } @@ -134,7 +137,7 @@ public static Header[] parseHeaders( * @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 java.io.InterruptedIOException}. + * headers in case of a {@link InterruptedIOException}. * * @return array of HTTP headers * diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/AbstractMessageWriter.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/AbstractMessageWriter.java index dcc6b0f8d8..6153aec3b8 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/AbstractMessageWriter.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/AbstractMessageWriter.java @@ -46,6 +46,7 @@ * 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 { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/BHttpConnectionBase.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/BHttpConnectionBase.java index f22ddc2fb3..8e88f22d99 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/BHttpConnectionBase.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/BHttpConnectionBase.java @@ -208,7 +208,7 @@ public void setSocketTimeout(final Timeout timeout) { final SocketHolder socketHolder = this.socketHolderRef.get(); if (socketHolder != null) { try { - socketHolder.getSocket().setSoTimeout(Timeout.defaultsToDisabled(timeout).toMillisecondsIntBound()); + 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 @@ -226,22 +226,41 @@ public Timeout getSocketTimeout() { } catch (final SocketException ignore) { } } - return Timeout.DISABLED; + return Timeout.INFINITE; } @Override public void close(final CloseMode closeMode) { final SocketHolder socketHolder = this.socketHolderRef.getAndSet(null); if (socketHolder != null) { - final Socket socket = socketHolder.getSocket(); - try { - if (closeMode == CloseMode.IMMEDIATE) { + final SSLSocket sslSocket = socketHolder.getSSLSocket(); + final Socket baseSocket = socketHolder.getBaseSocket(); + if (closeMode == CloseMode.IMMEDIATE) { + try { // force abortive close (RST) - socket.setSoLinger(true, 0); + baseSocket.setSoLinger(true, 0); + } catch (final IOException ignore) { + } finally { + Closer.closeQuietly(baseSocket); + } + } else { + // Close TLS layer first. + try { + if (sslSocket != null) { + try { + if (!sslSocket.isOutputShutdown()) { + sslSocket.shutdownOutput(); + } + if (!sslSocket.isInputShutdown()) { + sslSocket.shutdownInput(); + } + sslSocket.close(); + } catch (final IOException ignore) { + } + } + } finally { + Closer.closeQuietly(baseSocket); } - } catch (final IOException ignore) { - } finally { - Closer.closeQuietly(socket); } } } @@ -250,9 +269,13 @@ public void close(final CloseMode closeMode) { public void close() throws IOException { final SocketHolder socketHolder = this.socketHolderRef.getAndSet(null); if (socketHolder != null) { - try (final Socket socket = socketHolder.getSocket()) { + try (final Socket baseSocket = socketHolder.getBaseSocket()) { this.inBuffer.clear(); this.outbuffer.flush(socketHolder.getOutputStream()); + final SSLSocket sslSocket = socketHolder.getSSLSocket(); + if (sslSocket != null) { + sslSocket.close(); + } } } } @@ -337,7 +360,7 @@ public EndpointDetails getEndpointDetails() { try { socketTimeout = Timeout.ofMilliseconds(socket.getSoTimeout()); } catch (final SocketException e) { - socketTimeout = Timeout.DISABLED; + socketTimeout = Timeout.INFINITE; } endpointDetails = new BasicEndpointDetails( socket.getRemoteSocketAddress(), diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java index 96a32e45f8..45e8456c0d 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ChunkedInputStream.java @@ -170,12 +170,13 @@ public int read() throws IOException { * @throws IOException in case of an I/O error */ @Override - public int read (final byte[] b, final int off, final int len) throws IOException { - + 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; } @@ -206,7 +207,7 @@ public int read (final byte[] b, final int off, final int len) throws IOExceptio * @throws IOException in case of an I/O error */ @Override - public int read (final byte[] b) throws IOException { + public int read(final byte[] b) throws IOException { return read(b, 0, b.length); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ContentLengthInputStream.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ContentLengthInputStream.java index 56d405f218..3581ebd568 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ContentLengthInputStream.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/ContentLengthInputStream.java @@ -161,11 +161,12 @@ public int read(final byte[] b, final int off, final int len) throws java.io.IOE 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); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpClientConnection.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpClientConnection.java index 78c9aff74a..f798d90c79 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpClientConnection.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpClientConnection.java @@ -35,8 +35,11 @@ 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; @@ -57,7 +60,7 @@ 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.BasicTokenIterator; +import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.util.Args; /** @@ -108,13 +111,12 @@ public DefaultBHttpClientConnection( this.requestWriter = (requestWriterFactory != null ? requestWriterFactory : DefaultHttpRequestWriterFactory.INSTANCE).create(); this.responseParser = (responseParserFactory != null ? responseParserFactory : - DefaultHttpResponseParserFactory.INSTANCE).create(http1Config); + DefaultHttpResponseParserFactory.INSTANCE).create(); this.incomingContentStrategy = incomingContentStrategy != null ? incomingContentStrategy : DefaultContentLengthStrategy.INSTANCE; this.outgoingContentStrategy = outgoingContentStrategy != null ? outgoingContentStrategy : DefaultContentLengthStrategy.INSTANCE; - this.responseOutOfOrderStrategy = responseOutOfOrderStrategy != null ? responseOutOfOrderStrategy : - NoResponseOutOfOrderStrategy.INSTANCE; + this.responseOutOfOrderStrategy = responseOutOfOrderStrategy; this.consistent = true; } @@ -177,6 +179,14 @@ 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 { @@ -204,9 +214,16 @@ 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, @@ -220,21 +237,36 @@ void checkForEarlyResponse(final long totalBytesSent, final int nextWriteSize) t @Override public void write(final byte[] b) throws IOException { - checkForEarlyResponse(totalBytes, b.length); + 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 { - checkForEarlyResponse(totalBytes, len); + 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 { - checkForEarlyResponse(totalBytes, 1); + if (sslSocket != null) { + checkTLS(sslSocket); + } + if (responseOutOfOrderStrategy != null) { + checkForEarlyResponse(totalBytes, 1); + } totalBytes++; socketOutputStream.write(b); } @@ -271,9 +303,9 @@ public void terminateRequest(final ClassicHttpRequest request) throws HttpExcept if (entity == null) { return; } - final Iterator ti = new BasicTokenIterator(request.headerIterator(HttpHeaders.CONNECTION)); - while (ti.hasNext()) { - final String token = ti.next(); + 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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpClientConnectionFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpClientConnectionFactory.java index c2becd2a03..2f4ab30e83 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpClientConnectionFactory.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpClientConnectionFactory.java @@ -30,6 +30,8 @@ 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; @@ -73,8 +75,10 @@ private DefaultBHttpClientConnectionFactory( this.incomingContentStrategy = incomingContentStrategy; this.outgoingContentStrategy = outgoingContentStrategy; this.responseOutOfOrderStrategy = responseOutOfOrderStrategy; - this.requestWriterFactory = requestWriterFactory; - this.responseParserFactory = responseParserFactory; + this.requestWriterFactory = requestWriterFactory != null ? requestWriterFactory : + new DefaultHttpRequestWriterFactory(this.http1Config) ; + this.responseParserFactory = responseParserFactory != null ? responseParserFactory : + new DefaultHttpResponseParserFactory(this.http1Config); } public DefaultBHttpClientConnectionFactory( @@ -112,9 +116,8 @@ public DefaultBHttpClientConnectionFactory() { this(null, null, null, null, null, null); } - @Override - public DefaultBHttpClientConnection createConnection(final Socket socket) throws IOException { - final DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection( + DefaultBHttpClientConnection createDetached() { + return new DefaultBHttpClientConnection( this.http1Config, CharCodingSupport.createDecoder(this.charCodingConfig), CharCodingSupport.createEncoder(this.charCodingConfig), @@ -123,10 +126,25 @@ public DefaultBHttpClientConnection createConnection(final Socket socket) throws 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}. * diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpServerConnection.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpServerConnection.java index 8fe324f8b1..ab674c7da5 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpServerConnection.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpServerConnection.java @@ -33,6 +33,8 @@ 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; @@ -41,6 +43,7 @@ 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; @@ -93,9 +96,9 @@ public DefaultBHttpServerConnection( final HttpMessageParserFactory requestParserFactory, final HttpMessageWriterFactory responseWriterFactory) { super(http1Config, charDecoder, charEncoder); - this.scheme = scheme; + this.scheme = scheme != null ? scheme : URIScheme.HTTP.getId(); this.requestParser = (requestParserFactory != null ? requestParserFactory : - DefaultHttpRequestParserFactory.INSTANCE).create(http1Config); + DefaultHttpRequestParserFactory.INSTANCE).create(); this.responseWriter = (responseWriterFactory != null ? responseWriterFactory : DefaultHttpResponseWriterFactory.INSTANCE).create(); this.incomingContentStrategy = incomingContentStrategy != null ? incomingContentStrategy : @@ -129,6 +132,14 @@ 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(); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpServerConnectionFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpServerConnectionFactory.java index 8a67f1ed1c..4b84958dd5 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpServerConnectionFactory.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultBHttpServerConnectionFactory.java @@ -30,11 +30,14 @@ 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; @@ -72,8 +75,10 @@ public DefaultBHttpServerConnectionFactory( this.charCodingConfig = charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT; this.incomingContentStrategy = incomingContentStrategy; this.outgoingContentStrategy = outgoingContentStrategy; - this.requestParserFactory = requestParserFactory; - this.responseWriterFactory = responseWriterFactory; + this.requestParserFactory = requestParserFactory != null ? requestParserFactory : + new DefaultHttpRequestParserFactory(this.http1Config); + this.responseWriterFactory = responseWriterFactory != null ? responseWriterFactory : + new DefaultHttpResponseWriterFactory(this.http1Config); } public DefaultBHttpServerConnectionFactory( @@ -92,10 +97,9 @@ public DefaultBHttpServerConnectionFactory( this(scheme, http1Config, charCodingConfig, null, null, null, null); } - @Override - public DefaultBHttpServerConnection createConnection(final Socket socket) throws IOException { - final DefaultBHttpServerConnection conn = new DefaultBHttpServerConnection( - this.scheme, + 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), @@ -103,10 +107,25 @@ public DefaultBHttpServerConnection createConnection(final Socket socket) throws 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}. * diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestParser.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestParser.java index b5acd53cdb..19a1ecedb9 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestParser.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestParser.java @@ -52,37 +52,39 @@ public class DefaultHttpRequestParser extends AbstractMessageParser requestFactory; /** - * Creates new instance of DefaultHttpRequestParser. - * - * @param lineParser the line parser. If {@code null} - * {@link org.apache.hc.core5.http.message.LazyLineParser#INSTANCE} will be used. - * @param requestFactory the response factory. If {@code null} - * {@link DefaultClassicHttpRequestFactory#INSTANCE} will be used. - * @param http1Config the message http1Config. If {@code null} - * {@link Http1Config#DEFAULT} will be used. - * - * @since 4.3 + * @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) { - super(lineParser, http1Config); - this.requestFactory = requestFactory != null ? requestFactory : DefaultClassicHttpRequestFactory.INSTANCE; + this(http1Config, lineParser, requestFactory); } /** * @since 4.3 */ public DefaultHttpRequestParser(final Http1Config http1Config) { - this(null, null, http1Config); + this(http1Config, null, null); } /** * @since 4.3 */ public DefaultHttpRequestParser() { - this(Http1Config.DEFAULT); + this(Http1Config.DEFAULT, null, null); } @Override diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestParserFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestParserFactory.java index a2cbb983ea..ebace60630 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestParserFactory.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestParserFactory.java @@ -47,23 +47,51 @@ public class DefaultHttpRequestParserFactory implements HttpMessageParserFactory public static final DefaultHttpRequestParserFactory INSTANCE = new DefaultHttpRequestParserFactory(); + private final Http1Config http1Config; private final LineParser lineParser; private final HttpRequestFactory requestFactory; - public DefaultHttpRequestParserFactory(final LineParser lineParser, + /** + * @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(this.lineParser, this.requestFactory, http1Config); + return new DefaultHttpRequestParser(http1Config, this.lineParser, this.requestFactory); + } + + @Override + public HttpMessageParser create() { + return new DefaultHttpRequestParser(this.http1Config, this.lineParser, this.requestFactory); } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestWriter.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestWriter.java index 5aac8da080..c5afc4fd0a 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestWriter.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestWriter.java @@ -30,8 +30,9 @@ 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.ProtocolVersion; +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; @@ -44,29 +45,41 @@ */ public class DefaultHttpRequestWriter extends AbstractMessageWriter { + private final Http1Config http1Config; + /** - * 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 5.3 */ - public DefaultHttpRequestWriter(final LineFormatter formatter) { + 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); + 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 { - final ProtocolVersion transportVersion = message.getVersion(); getLineFormatter().formatRequestLine(lineBuf, new RequestLine( message.getMethod(), message.getRequestUri(), - transportVersion != null ? transportVersion : HttpVersion.HTTP_1_1)); + protocolVersion(message))); } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestWriterFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestWriterFactory.java index b377ccc28b..c38d4b377e 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestWriterFactory.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestWriterFactory.java @@ -30,6 +30,7 @@ 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; @@ -45,20 +46,36 @@ public class DefaultHttpRequestWriterFactory implements HttpMessageWriterFactory public static final DefaultHttpRequestWriterFactory INSTANCE = new DefaultHttpRequestWriterFactory(); + private final Http1Config http1Config; private final LineFormatter lineFormatter; - public DefaultHttpRequestWriterFactory(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); + this(null, null); } @Override public HttpMessageWriter create() { - return new DefaultHttpRequestWriter(this.lineFormatter); + return new DefaultHttpRequestWriter(http1Config, lineFormatter); } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseParser.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseParser.java index c2c4352f5f..32d7810c0f 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseParser.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseParser.java @@ -48,30 +48,32 @@ public class DefaultHttpResponseParser extends AbstractMessageParser responseFactory; /** - * Creates new instance of DefaultHttpResponseParser. - * - * @param lineParser the line parser. If {@code null} - * {@link org.apache.hc.core5.http.message.LazyLineParser#INSTANCE} will be used - * @param responseFactory the response factory. If {@code null} - * {@link DefaultClassicHttpResponseFactory#INSTANCE} will be used. - * @param http1Config the message http1Config. If {@code null} - * {@link Http1Config#DEFAULT} will be used. - * - * @since 4.3 + * @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) { - super(lineParser, http1Config); - this.responseFactory = responseFactory != null ? responseFactory : DefaultClassicHttpResponseFactory.INSTANCE; + this(http1Config, lineParser, responseFactory); } /** * @since 4.3 */ public DefaultHttpResponseParser(final Http1Config http1Config) { - this(null, null, http1Config); + this(http1Config, null, null); } /** diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseParserFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseParserFactory.java index 9e602f7899..59422fb7be 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseParserFactory.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseParserFactory.java @@ -47,23 +47,51 @@ public class DefaultHttpResponseParserFactory implements HttpMessageParserFactor public static final DefaultHttpResponseParserFactory INSTANCE = new DefaultHttpResponseParserFactory(); + private final Http1Config http1Config; private final LineParser lineParser; private final HttpResponseFactory responseFactory; - public DefaultHttpResponseParserFactory(final LineParser lineParser, + /** + * @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(this.lineParser, this.responseFactory, http1Config); + return new DefaultHttpResponseParser(http1Config, this.lineParser, this.responseFactory); + } + + @Override + public HttpMessageParser create() { + return new DefaultHttpResponseParser(this.http1Config, this.lineParser, this.responseFactory); } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseWriter.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseWriter.java index 478a4aa483..8f56671049 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseWriter.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseWriter.java @@ -30,8 +30,9 @@ 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.ProtocolVersion; +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; @@ -44,27 +45,39 @@ */ public class DefaultHttpResponseWriter extends AbstractMessageWriter { + private final Http1Config http1Config; + /** - * 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 5.3 */ - public DefaultHttpResponseWriter(final LineFormatter formatter) { + 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() { - super(null); + 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 { - final ProtocolVersion transportVersion = message.getVersion(); getLineFormatter().formatStatusLine(lineBuf, new StatusLine( - transportVersion != null ? transportVersion : HttpVersion.HTTP_1_1, + protocolVersion(message), message.getCode(), message.getReasonPhrase())); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseWriterFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseWriterFactory.java index beac2f5657..75ae97a16e 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseWriterFactory.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpResponseWriterFactory.java @@ -30,6 +30,7 @@ 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; @@ -45,20 +46,36 @@ public class DefaultHttpResponseWriterFactory implements HttpMessageWriterFactor public static final DefaultHttpResponseWriterFactory INSTANCE = new DefaultHttpResponseWriterFactory(); + private final Http1Config http1Config; private final LineFormatter lineFormatter; - public DefaultHttpResponseWriterFactory(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); + this(null, null); } @Override public HttpMessageWriter create() { - return new DefaultHttpResponseWriter(this.lineFormatter); + return new DefaultHttpResponseWriter(http1Config, lineFormatter); } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/EmptyInputStream.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/EmptyInputStream.java index 1c3d2e4429..9608ef6b19 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/EmptyInputStream.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/EmptyInputStream.java @@ -96,6 +96,9 @@ public int read(final byte[] buf) { */ @Override public int read(final byte[] buf, final int off, final int len) { + if (len == 0) { + return 0; + } return -1; } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpRequestExecutor.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpRequestExecutor.java index 7f6ad67961..e97f79fa9c 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpRequestExecutor.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpRequestExecutor.java @@ -44,6 +44,7 @@ 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; @@ -75,31 +76,46 @@ public class HttpRequestExecutor { public static final Timeout DEFAULT_WAIT_FOR_CONTINUE = Timeout.ofSeconds(3); - private final Timeout waitForContinue; + private final Http1Config http1Config; private final ConnectionReuseStrategy connReuseStrategy; private final Http1StreamListener streamListener; /** * Creates new instance of HttpRequestExecutor. * - * @since 4.3 + * @since 5.3 */ public HttpRequestExecutor( - final Timeout waitForContinue, + final Http1Config http1Config, final ConnectionReuseStrategy connReuseStrategy, final Http1StreamListener streamListener) { super(); - this.waitForContinue = Args.positive(waitForContinue, "Wait for continue time"); + 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(DEFAULT_WAIT_FOR_CONTINUE, connReuseStrategy, null); + this(Http1Config.DEFAULT, connReuseStrategy, null); } public HttpRequestExecutor() { - this(DEFAULT_WAIT_FOR_CONTINUE, null, null); + this(Http1Config.DEFAULT, null, null); } /** @@ -109,7 +125,7 @@ public HttpRequestExecutor() { * @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 context the context + * @param localContext the context * @return the response to the request. * * @throws IOException in case of an I/O error. @@ -120,13 +136,14 @@ public ClassicHttpResponse execute( final ClassicHttpRequest request, final HttpClientConnection conn, final HttpResponseInformationCallback informationCallback, - final HttpContext context) throws IOException, HttpException { + final HttpContext localContext) throws IOException, HttpException { Args.notNull(request, "HTTP request"); Args.notNull(conn, "Client connection"); - Args.notNull(context, "HTTP context"); + Args.notNull(localContext, "HTTP context"); + final HttpCoreContext context = HttpCoreContext.castOrCreate(localContext); try { - context.setAttribute(HttpCoreContext.SSL_SESSION, conn.getSSLSession()); - context.setAttribute(HttpCoreContext.CONNECTION_ENDPOINT, conn.getEndpointDetails()); + context.setSSLSession(conn.getSSLSession()); + context.setEndpointDetails(conn.getEndpointDetails()); conn.sendRequestHeader(request); if (streamListener != null) { @@ -145,7 +162,8 @@ public ClassicHttpResponse execute( ClassicHttpResponse response = null; while (response == null) { if (expectContinue) { - if (conn.isDataAvailable(this.waitForContinue)) { + 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); @@ -224,7 +242,7 @@ public ClassicHttpResponse execute( * * @param request the request to prepare * @param processor the processor to use - * @param context the context for sending the request + * @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 @@ -233,16 +251,17 @@ public ClassicHttpResponse execute( public void preProcess( final ClassicHttpRequest request, final HttpProcessor processor, - final HttpContext context) throws HttpException, IOException { + final HttpContext localContext) throws HttpException, IOException { Args.notNull(request, "HTTP request"); Args.notNull(processor, "HTTP processor"); - Args.notNull(context, "HTTP context"); + Args.notNull(localContext, "HTTP context"); final ProtocolVersion transportVersion = request.getVersion(); - if (transportVersion != null && transportVersion.greaterEquals(HttpVersion.HTTP_2)) { + if (transportVersion != null && !transportVersion.lessEquals(http1Config.getVersion())) { throw new UnsupportedHttpVersionException(transportVersion); } - context.setProtocolVersion(transportVersion != null ? transportVersion : HttpVersion.HTTP_1_1); - context.setAttribute(HttpCoreContext.HTTP_REQUEST, request); + final HttpCoreContext context = HttpCoreContext.cast(localContext); + context.setProtocolVersion(transportVersion != null ? transportVersion : http1Config.getVersion()); + context.setRequest(request); processor.process(request, request.getEntity(), context); } @@ -257,7 +276,7 @@ public void preProcess( * * @param response the response object to post-process * @param processor the processor to use - * @param context the context for post-processing the response + * @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 @@ -266,13 +285,19 @@ public void preProcess( public void postProcess( final ClassicHttpResponse response, final HttpProcessor processor, - final HttpContext context) throws HttpException, IOException { + final HttpContext localContext) throws HttpException, IOException { Args.notNull(response, "HTTP response"); Args.notNull(processor, "HTTP processor"); - Args.notNull(context, "HTTP context"); + Args.notNull(localContext, "HTTP context"); + final HttpCoreContext context = HttpCoreContext.cast(localContext); final ProtocolVersion transportVersion = response.getVersion(); - context.setProtocolVersion(transportVersion != null ? transportVersion : HttpVersion.HTTP_1_1); - context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response); + 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); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpService.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpService.java index 3a7c18218b..d21f69b935 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpService.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpService.java @@ -45,6 +45,7 @@ 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; @@ -82,13 +83,13 @@ 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} @@ -105,6 +106,7 @@ public HttpService( final Http1StreamListener streamListener) { this(processor, new BasicHttpServerExpectationDecorator(new BasicHttpServerRequestHandler(handlerMapper, responseFactory)), + Http1Config.DEFAULT, connReuseStrategy, streamListener); } @@ -141,9 +143,31 @@ public HttpService( 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; } @@ -156,7 +180,7 @@ public HttpService( */ public HttpService( final HttpProcessor processor, final HttpServerRequestHandler requestHandler) { - this(processor, requestHandler, null, null); + this(processor, requestHandler, Http1Config.DEFAULT, null, null); } /** @@ -164,16 +188,17 @@ public HttpService( * given execution context and sends a response back to the client. * * @param conn the active connection to the client - * @param context the actual execution context. + * @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 context) throws IOException, HttpException { + final HttpContext localContext) throws IOException, HttpException { - final AtomicBoolean responseSubmitted = new AtomicBoolean(false); + final AtomicBoolean responseSubmitted = new AtomicBoolean(); + final HttpCoreContext context = HttpCoreContext.cast(localContext); try { final ClassicHttpRequest request = conn.receiveRequestHeader(); if (request == null) { @@ -185,10 +210,13 @@ public void handleRequest( } conn.receiveRequestEntity(request); final ProtocolVersion transportVersion = request.getVersion(); - context.setProtocolVersion(transportVersion != null ? transportVersion : HttpVersion.HTTP_1_1); - context.setAttribute(HttpCoreContext.SSL_SESSION, conn.getSSLSession()); - context.setAttribute(HttpCoreContext.CONNECTION_ENDPOINT, conn.getEndpointDetails()); - context.setAttribute(HttpCoreContext.HTTP_REQUEST, request); + 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() { @@ -212,12 +240,13 @@ public void sendInformation(final ClassicHttpResponse response) throws HttpExcep public void submitResponse(final ClassicHttpResponse response) throws HttpException, IOException { try { final ProtocolVersion transportVersion = response.getVersion(); - if (transportVersion != null && transportVersion.greaterEquals(HttpVersion.HTTP_2)) { - throw new UnsupportedHttpVersionException(transportVersion); + if (transportVersion != null) { + if (!transportVersion.lessEquals(http1Config.getVersion())) { + throw new UnsupportedHttpVersionException(transportVersion); + } + context.setProtocolVersion(transportVersion); } - ServerSupport.validateResponse(response, response.getEntity()); - context.setProtocolVersion(transportVersion != null ? transportVersion : HttpVersion.HTTP_1_1); - context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response); + context.setResponse(response); processor.process(response, response.getEntity(), context); responseSubmitted.set(true); @@ -252,7 +281,7 @@ public void submitResponse(final ClassicHttpResponse response) throws HttpExcept try (final ClassicHttpResponse errorResponse = new BasicClassicHttpResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR)) { handleException(ex, errorResponse); errorResponse.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); - context.setAttribute(HttpCoreContext.HTTP_RESPONSE, errorResponse); + context.setResponse(errorResponse); this.processor.process(errorResponse, errorResponse.getEntity(), context); conn.sendResponseHeader(errorResponse); @@ -301,6 +330,7 @@ public static final class Builder { private HttpProcessor processor; private HttpServerRequestHandler requestHandler; + private Http1Config http1Config; private ConnectionReuseStrategy connReuseStrategy; private Http1StreamListener streamListener; @@ -316,6 +346,11 @@ public Builder withHttpServerRequestHandler(final HttpServerRequestHandler reque return this; } + public Builder withHttp1Config(final Http1Config http1Config) { + this.http1Config = http1Config; + return this; + } + public Builder withConnectionReuseStrategy(final ConnectionReuseStrategy connReuseStrategy) { this.connReuseStrategy = connReuseStrategy; return this; @@ -325,6 +360,7 @@ public Builder withHttp1StreamListener(final Http1StreamListener streamListener) this.streamListener = streamListener; return this; } + /** * Create a new HTTP service. * @@ -334,6 +370,7 @@ public HttpService build() { return new HttpService( processor, requestHandler, + http1Config, connReuseStrategy, streamListener); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/IdentityInputStream.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/IdentityInputStream.java index acc294fec3..49f3ea965f 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/IdentityInputStream.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/IdentityInputStream.java @@ -96,6 +96,9 @@ 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); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/IncomingHttpEntity.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/IncomingHttpEntity.java index ee1228f899..91edff19f8 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/IncomingHttpEntity.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/IncomingHttpEntity.java @@ -38,7 +38,6 @@ import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.io.entity.AbstractHttpEntity; -import org.apache.hc.core5.http.io.entity.EmptyInputStream; import org.apache.hc.core5.io.Closer; class IncomingHttpEntity implements HttpEntity { @@ -89,7 +88,7 @@ public InputStream getContent() throws IOException, IllegalStateException { @Override public boolean isStreaming() { - return content != null && content != EmptyInputStream.INSTANCE; + return content != null; } @Override diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/SessionInputBufferImpl.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/SessionInputBufferImpl.java index 491fe75e7d..a0862b37d0 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/SessionInputBufferImpl.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/SessionInputBufferImpl.java @@ -180,7 +180,7 @@ public int read(final InputStream inputStream) throws IOException { @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) { + if (b == null || b.length == 0 || len == 0) { return 0; } if (hasBufferedData()) { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/SocketHolder.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/SocketHolder.java index b189dc5942..114034d425 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/SocketHolder.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/SocketHolder.java @@ -33,6 +33,9 @@ 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; /** @@ -43,18 +46,46 @@ */ public class SocketHolder { - private final Socket socket; + 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.socket = Args.notNull(socket, "Socket"); + this.baseSocket = Args.notNull(socket, "Socket"); + this.sslSocket = null; this.inputStreamRef = new AtomicReference<>(); this.outputStreamRef = new AtomicReference<>(); } public final Socket getSocket() { - return socket; + 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 { @@ -62,7 +93,7 @@ public final InputStream getInputStream() throws IOException { if (local != null) { return local; } - local = getInputStream(socket); + local = getInputStream(getSocket()); if (inputStreamRef.compareAndSet(null, local)) { return local; } @@ -82,7 +113,7 @@ public final OutputStream getOutputStream() throws IOException { if (local != null) { return local; } - local = getOutputStream(socket); + local = getOutputStream(getSocket()); if (outputStreamRef.compareAndSet(null, local)) { return local; } @@ -91,7 +122,10 @@ public final OutputStream getOutputStream() throws IOException { @Override public String toString() { - return socket.toString(); + return "SocketHolder{" + + "sslSocket=" + sslSocket + + ", baseSocket=" + baseSocket + + '}'; } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractContentDecoder.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractContentDecoder.java index ff25a1af13..07952284a3 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractContentDecoder.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractContentDecoder.java @@ -122,6 +122,7 @@ protected void setCompleted() { * * @param dst destination. * @return number of bytes transferred. + * @throws IOException If an I/O error occurs. * * @since 4.3 */ @@ -135,7 +136,9 @@ protected int readFromChannel(final ByteBuffer dst) throws IOException { /** * Reads from the channel to the session buffer. - * @return number of bytes transferred. + * + * @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 */ @@ -152,7 +155,8 @@ protected int fillBufferFromChannel() throws IOException { * * @param dst destination. * @param limit max number of bytes to transfer. - * @return number of bytes transferred. + * @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 */ diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractContentEncoder.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractContentEncoder.java index 4b03fa82c8..4ebf3ea322 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractContentEncoder.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractContentEncoder.java @@ -108,7 +108,7 @@ protected void assertNotCompleted() { * 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 { @@ -126,7 +126,7 @@ protected int flushToChannel() throws IOException { * 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 { @@ -146,7 +146,7 @@ protected int writeToChannel(final ByteBuffer src) throws IOException { * @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 { @@ -159,7 +159,7 @@ protected int writeToChannel(final ByteBuffer src, final int limit) throws IOExc * @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 { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractHttp1StreamDuplexer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractHttp1StreamDuplexer.java index b60adf2dcc..b5dfbdc75b 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractHttp1StreamDuplexer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractHttp1StreamDuplexer.java @@ -30,13 +30,14 @@ import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; -import java.nio.channels.ClosedChannelException; 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 javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; import org.apache.hc.core5.http.ConnectionClosedException; @@ -166,6 +167,8 @@ void shutdownSession(final Exception cause) { 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 { @@ -498,7 +501,7 @@ int streamOutput(final ByteBuffer src) throws IOException { ioSession.getLock().lock(); try { if (outgoingMessage == null) { - throw new ClosedChannelException(); + throw new ConnectionClosedException(); } final ContentEncoder contentEncoder = outgoingMessage.getBody(); final int bytesWritten = contentEncoder.write(src); @@ -611,19 +614,20 @@ void appendState(final StringBuilder buf) { static class CapacityWindow implements CapacityChannel { private final IOSession ioSession; - private final Object lock; + 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 Object(); + this.lock = new ReentrantLock(); } @Override public void update(final int increment) throws IOException { - synchronized (lock) { + lock.lock(); + try { if (closed) { return; } @@ -631,6 +635,8 @@ public void update(final int increment) throws IOException { updateWindow(increment); ioSession.setEvent(SelectionKey.OP_READ); } + } finally { + lock.unlock(); } } @@ -639,12 +645,15 @@ public void update(final int increment) throws IOException { * if this channel is closed in it. */ int removeCapacity(final int delta) { - synchronized (lock) { + lock.lock(); + try { updateWindow(-delta); if (window <= 0) { ioSession.clearEvent(SelectionKey.OP_READ); } return window; + } finally { + lock.unlock(); } } @@ -662,8 +671,11 @@ private void updateWindow(final int delta) { * read events outside of the context of the request the channel was created for */ void close() { - synchronized (lock) { + lock.lock(); + try { closed = true; + } finally { + lock.unlock(); } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractMessageParser.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractMessageParser.java index 9c6dc65fab..1582da3721 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractMessageParser.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractMessageParser.java @@ -37,8 +37,8 @@ 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.SessionInputBuffer; 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; @@ -46,6 +46,7 @@ * 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 { @@ -54,6 +55,9 @@ private enum State { READ_HEAD_LINE, READ_HEADERS, COMPLETED } + private final Http1Config http1Config; + private final LineParser lineParser; + private State state; private T message; @@ -61,27 +65,25 @@ private enum State { private final List headerBufs; private int emptyLineCount; - private final LineParser lineParser; - private final Http1Config messageConstraints; - /** - * Creates an instance of AbstractMessageParser. - * - * @param lineParser the line parser. If {@code null} - * {@link org.apache.hc.core5.http.message.LazyLineParser#INSTANCE} will be used. - * @param messageConstraints Message constraints. If {@code null} - * {@link Http1Config#DEFAULT} will be used. - * - * @since 4.3 + * @since 5.3 */ - public AbstractMessageParser(final LineParser lineParser, final Http1Config messageConstraints) { + public AbstractMessageParser(final Http1Config http1Config, final LineParser lineParser) { super(); + this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.lineParser = lineParser != null ? lineParser : LazyLineParser.INSTANCE; - this.messageConstraints = messageConstraints != null ? messageConstraints : Http1Config.DEFAULT; this.headerBufs = new ArrayList<>(); this.state = State.READ_HEAD_LINE; } + /** + * @deprecated Use {@link #AbstractMessageParser(Http1Config, LineParser)} + */ + @Deprecated + public AbstractMessageParser(final LineParser lineParser, final Http1Config messageConstraints) { + this(messageConstraints, lineParser); + } + LineParser getLineParser() { return this.lineParser; } @@ -107,7 +109,7 @@ public void reset() { private T parseHeadLine() throws IOException, HttpException { if (this.lineBuf.isEmpty()) { this.emptyLineCount++; - if (this.emptyLineCount >= this.messageConstraints.getMaxEmptyLineCount()) { + if (this.emptyLineCount >= this.http1Config.getMaxEmptyLineCount()) { throw new MessageConstraintException("Maximum empty line limit exceeded"); } return null; @@ -129,7 +131,7 @@ private void parseHeader() throws IOException { } i++; } - final int maxLineLen = this.messageConstraints.getMaxLineLength(); + final int maxLineLen = this.http1Config.getMaxLineLength(); if (maxLineLen > 0 && previous.length() + 1 + current.length() - i > maxLineLen) { throw new MessageConstraintException("Maximum line length limit exceeded"); } @@ -152,7 +154,7 @@ public T parse( this.lineBuf.clear(); } final boolean lineComplete = sessionBuffer.readLine(this.lineBuf, endOfStream); - final int maxLineLen = this.messageConstraints.getMaxLineLength(); + final int maxLineLen = this.http1Config.getMaxLineLength(); if (maxLineLen > 0 && (this.lineBuf.length() > maxLineLen || (!lineComplete && sessionBuffer.length() > maxLineLen))) { @@ -171,7 +173,7 @@ public T parse( break; case READ_HEADERS: if (this.lineBuf.length() > 0) { - final int maxHeaderCount = this.messageConstraints.getMaxHeaderCount(); + final int maxHeaderCount = this.http1Config.getMaxHeaderCount(); if (maxHeaderCount > 0 && headerBufs.size() >= maxHeaderCount) { throw new MessageConstraintException("Maximum header count exceeded"); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractMessageWriter.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractMessageWriter.java index f3c2823b5a..16bd77f022 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractMessageWriter.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractMessageWriter.java @@ -36,8 +36,8 @@ 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.SessionOutputBuffer; 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; @@ -45,6 +45,7 @@ * 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 { @@ -78,6 +79,7 @@ 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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamDuplexer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamDuplexer.java index 614056c539..7299d7251d 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamDuplexer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamDuplexer.java @@ -309,9 +309,9 @@ void outputEnd() throws HttpException, IOException { @Override void execute(final RequestExecutionCommand executionCommand) throws HttpException, IOException { final AsyncClientExchangeHandler exchangeHandler = executionCommand.getExchangeHandler(); - final HttpCoreContext context = HttpCoreContext.adapt(executionCommand.getContext()); - context.setAttribute(HttpCoreContext.SSL_SESSION, getSSLSession()); - context.setAttribute(HttpCoreContext.CONNECTION_ENDPOINT, getEndpointDetails()); + final HttpCoreContext context = HttpCoreContext.castOrCreate(executionCommand.getContext()); + context.setSSLSession(getSSLSession()); + context.setEndpointDetails(getEndpointDetails()); final ClientHttp1StreamHandler handler = new ClientHttp1StreamHandler( outputChannel, httpProcessor, diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamDuplexerFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamDuplexerFactory.java index 12544e8bc2..54691487e2 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamDuplexerFactory.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamDuplexerFactory.java @@ -80,9 +80,9 @@ public ClientHttp1StreamDuplexerFactory( this.connectionReuseStrategy = connectionReuseStrategy != null ? connectionReuseStrategy : DefaultConnectionReuseStrategy.INSTANCE; this.responseParserFactory = responseParserFactory != null ? responseParserFactory : - new DefaultHttpResponseParserFactory(http1Config); + new DefaultHttpResponseParserFactory(this.http1Config); this.requestWriterFactory = requestWriterFactory != null ? requestWriterFactory : - DefaultHttpRequestWriterFactory.INSTANCE; + new DefaultHttpRequestWriterFactory(this.http1Config); this.incomingContentStrategy = incomingContentStrategy != null ? incomingContentStrategy : DefaultContentLengthStrategy.INSTANCE; this.outgoingContentStrategy = outgoingContentStrategy != null ? outgoingContentStrategy : diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamHandler.java index 244a696e9c..62dd4c242e 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamHandler.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamHandler.java @@ -56,6 +56,8 @@ 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; @@ -110,8 +112,8 @@ public void endStream() throws IOException { this.connectionReuseStrategy = connectionReuseStrategy; this.exchangeHandler = exchangeHandler; this.context = context; - this.requestCommitted = new AtomicBoolean(false); - this.done = new AtomicBoolean(false); + this.requestCommitted = new AtomicBoolean(); + this.done = new AtomicBoolean(); this.keepAlive = true; this.requestState = MessageState.IDLE; this.responseState = MessageState.HEADERS; @@ -144,11 +146,11 @@ boolean isOutputReady() { 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.greaterEquals(HttpVersion.HTTP_2)) { + if (transportVersion != null && !transportVersion.lessEquals(http1Config.getVersion())) { throw new UnsupportedHttpVersionException(transportVersion); } - context.setProtocolVersion(transportVersion != null ? transportVersion : HttpVersion.HTTP_1_1); - context.setAttribute(HttpCoreContext.HTTP_REQUEST, request); + context.setProtocolVersion(transportVersion != null ? transportVersion : http1Config.getVersion()); + context.setRequest(request); httpProcessor.process(request, entityDetails, context); @@ -165,7 +167,8 @@ private void commitRequest(final HttpRequest request, final EntityDetails entity if (expectContinue) { requestState = MessageState.ACK; timeout = outputChannel.getSocketTimeout(); - outputChannel.setSocketTimeout(http1Config.getWaitForContinueTimeout()); + final Timeout timeout = http1Config.getWaitForContinueTimeout() != null ? http1Config.getWaitForContinueTimeout() : DEFAULT_WAIT_FOR_CONTINUE; + outputChannel.setSocketTimeout(timeout); } else { requestState = MessageState.BODY; exchangeHandler.produce(internalDataChannel); @@ -196,8 +199,11 @@ void consumeHeader(final HttpResponse response, final EntityDetails entityDetail throw new ProtocolException("Unexpected message head"); } final ProtocolVersion transportVersion = response.getVersion(); - if (transportVersion != null && transportVersion.greaterEquals(HttpVersion.HTTP_2)) { - throw new UnsupportedHttpVersionException(transportVersion); + if (transportVersion != null) { + if (transportVersion.greaterEquals(HttpVersion.HTTP_2)) { + throw new UnsupportedHttpVersionException(transportVersion); + } + context.setProtocolVersion(transportVersion); } final int status = response.getCode(); @@ -233,7 +239,7 @@ void consumeHeader(final HttpResponse response, final EntityDetails entityDetail } context.setProtocolVersion(transportVersion != null ? transportVersion : HttpVersion.HTTP_1_1); - context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response); + context.setResponse(response); httpProcessor.process(response, entityDetails, context); if (entityDetails == null && !keepAlive) { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestParser.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestParser.java index 67724df134..3fd3ab724c 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestParser.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestParser.java @@ -44,6 +44,7 @@ /** * 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 { @@ -51,36 +52,47 @@ public class DefaultHttpRequestParser extends AbstractMes private final HttpRequestFactory requestFactory; /** - * Creates an instance of DefaultHttpRequestParser. - * - * @param requestFactory the request factory. - * @param parser the line parser. If {@code null} - * {@link org.apache.hc.core5.http.message.LazyLineParser#INSTANCE} will be used. - * @param http1Config Message http1Config. If {@code null} - * {@link Http1Config#DEFAULT} will be used. - * - * @since 4.3 + * @since 5.3 */ public DefaultHttpRequestParser( - final HttpRequestFactory requestFactory, + final Http1Config http1Config, final LineParser parser, - final Http1Config http1Config) { - super(parser, http1Config); + final HttpRequestFactory requestFactory) { + super(http1Config, parser); this.requestFactory = Args.notNull(requestFactory, "Request factory"); } /** - * @since 4.3 - */ + * @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); } /** - * @since 4.3 - */ + * @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(requestFactory, null); + this(null, null, requestFactory); } @Override diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestParserFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestParserFactory.java index e0db9ab3b5..d3ba90f814 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestParserFactory.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestParserFactory.java @@ -71,7 +71,7 @@ public DefaultHttpRequestParserFactory() { @Override public NHttpMessageParser create() { - return new DefaultHttpRequestParser<>(requestFactory, lineParser, http1Config); + return new DefaultHttpRequestParser<>(http1Config, lineParser, requestFactory); } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestWriter.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestWriter.java index 480d4a32e9..179dfa86fe 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestWriter.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestWriter.java @@ -31,7 +31,7 @@ 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.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; @@ -39,10 +39,21 @@ /** * 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. * @@ -52,24 +63,33 @@ public class DefaultHttpRequestWriter extends AbstractMes * @since 4.3 */ public DefaultHttpRequestWriter(final LineFormatter formatter) { - super(formatter); + this(null, formatter); } /** * @since 4.3 */ public DefaultHttpRequestWriter() { - super(null); + 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(); - final ProtocolVersion transportVersion = message.getVersion(); getLineFormatter().formatRequestLine(lineBuf, new RequestLine( message.getMethod(), message.getRequestUri(), - transportVersion != null ? transportVersion : HttpVersion.HTTP_1_1)); + protocolVersion(message))); } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestWriterFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestWriterFactory.java index 013b16adf4..c71a557b0c 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestWriterFactory.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestWriterFactory.java @@ -30,6 +30,7 @@ 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; @@ -45,20 +46,36 @@ public class DefaultHttpRequestWriterFactory implements NHttpMessageWriterFactor public static final DefaultHttpRequestWriterFactory INSTANCE = new DefaultHttpRequestWriterFactory(); + private final Http1Config http1Config; private final LineFormatter lineFormatter; - public DefaultHttpRequestWriterFactory(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); + this(null, null); } @Override public NHttpMessageWriter create() { - return new DefaultHttpRequestWriter<>(this.lineFormatter); + return new DefaultHttpRequestWriter<>(http1Config, lineFormatter); } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseParser.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseParser.java index ec05fad048..405eba4cf2 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseParser.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseParser.java @@ -39,6 +39,7 @@ /** * 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 { @@ -46,27 +47,40 @@ public class DefaultHttpResponseParser extends AbstractM private final HttpResponseFactory responseFactory; /** - * Creates an instance of DefaultHttpResponseParser. - * - * @param responseFactory the response factory. - * @param parser the line parser. If {@code null} - * {@link org.apache.hc.core5.http.message.LazyLineParser#INSTANCE} will be used. - * @param http1Config Message http1Config. If {@code null} - * {@link Http1Config#DEFAULT} will be used. - * - * @since 4.3 + * @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) { - super(parser, http1Config); - this.responseFactory = Args.notNull(responseFactory, "Response factory"); + this(http1Config, parser, responseFactory); } /** - * @since 4.3 + * @deprecated Use {@link #DefaultHttpResponseParser(Http1Config, HttpResponseFactory)} */ + @Deprecated public DefaultHttpResponseParser(final HttpResponseFactory responseFactory, final Http1Config http1Config) { this(responseFactory, null, http1Config); } @@ -75,7 +89,7 @@ public DefaultHttpResponseParser(final HttpResponseFactory responseFactory, f * @since 4.3 */ public DefaultHttpResponseParser(final HttpResponseFactory responseFactory) { - this(responseFactory, null); + this(null, null, responseFactory); } @Override diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseParserFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseParserFactory.java index a6c8b3d319..01fcb03ef2 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseParserFactory.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseParserFactory.java @@ -71,7 +71,7 @@ public DefaultHttpResponseParserFactory() { @Override public NHttpMessageParser create() { - return new DefaultHttpResponseParser<>(responseFactory, lineParser, http1Config); + return new DefaultHttpResponseParser<>(http1Config, lineParser, responseFactory); } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseWriter.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseWriter.java index ea9922bca0..7e2b37e261 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseWriter.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseWriter.java @@ -31,7 +31,7 @@ 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.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; @@ -39,10 +39,21 @@ /** * 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. * @@ -52,22 +63,31 @@ public class DefaultHttpResponseWriter extends AbstractM * @since 4.3 */ public DefaultHttpResponseWriter(final LineFormatter formatter) { - super(formatter); + this(formatter, null); } /** * @since 4.3 */ public DefaultHttpResponseWriter() { - super(null); + 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(); - final ProtocolVersion transportVersion = message.getVersion(); getLineFormatter().formatStatusLine(lineBuf, new StatusLine( - transportVersion != null ? transportVersion : HttpVersion.HTTP_1_1, + protocolVersion(message), message.getCode(), message.getReasonPhrase())); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseWriterFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseWriterFactory.java index 6c08c655fe..9cd5d63f46 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseWriterFactory.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpResponseWriterFactory.java @@ -30,6 +30,7 @@ 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; @@ -45,20 +46,36 @@ public class DefaultHttpResponseWriterFactory implements NHttpMessageWriterFacto public static final DefaultHttpResponseWriterFactory INSTANCE = new DefaultHttpResponseWriterFactory(); + private final Http1Config http1Config; private final LineFormatter lineFormatter; - public DefaultHttpResponseWriterFactory(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); + this(null, null); } @Override public NHttpMessageWriter create() { - return new DefaultHttpResponseWriter<>(this.lineFormatter); + return new DefaultHttpResponseWriter<>(lineFormatter, http1Config); } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ExpandableBuffer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ExpandableBuffer.java index 35b105d0ef..9de871dd43 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ExpandableBuffer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ExpandableBuffer.java @@ -43,8 +43,16 @@ @Internal public class ExpandableBuffer { + /** + * A buffer's mode. + */ public enum Mode { - INPUT, OUTPUT + + /** A buffer is in the input mode. */ + INPUT, + + /** A buffer is in the output mode. */ + OUTPUT } private Mode mode; @@ -53,7 +61,7 @@ public enum Mode { /** * Allocates buffer of the given size using the given allocator. *

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

* * @param bufferSize the buffer size. @@ -66,12 +74,12 @@ protected ExpandableBuffer(final int bufferSize) { /** * Returns the current mode: - *

- * {@link Mode#INPUT}: the buffer is in the input mode. - *

- * {@link Mode#OUTPUT}: the buffer is in the output 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. + * @return current input/output mode, never null. */ protected Mode mode() { return this.mode; @@ -82,7 +90,7 @@ protected ByteBuffer buffer() { } /** - * Sets the mode to output. The buffer can now be read from. + * Sets the mode to {@link Mode#OUTPUT OUTPUT}. The buffer can now be read from. */ protected void setOutputMode() { if (this.mode != Mode.OUTPUT) { @@ -92,7 +100,7 @@ protected void setOutputMode() { } /** - * Sets the mode to input. The buffer can now be written into. + * Sets the mode to {@link Mode#INPUT INPUT}. The buffer can now be written into. */ protected void setInputMode() { if (this.mode != Mode.INPUT) { @@ -204,7 +212,7 @@ protected int capacity() { /** * Clears buffer. *

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

*/ protected void clear() { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamDuplexer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamDuplexer.java index fafd72fc62..4dc3bf4c05 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamDuplexer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamDuplexer.java @@ -34,6 +34,7 @@ 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.http.ConnectionClosedException; @@ -45,6 +46,7 @@ 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; @@ -108,7 +110,7 @@ public ServerHttp1StreamDuplexer( incomingContentStrategy, outgoingContentStrategy); this.httpProcessor = Args.notNull(httpProcessor, "HTTP processor"); this.exchangeHandlerFactory = Args.notNull(exchangeHandlerFactory, "Exchange handler factory"); - this.scheme = scheme; + this.scheme = scheme != null ? scheme : URIScheme.HTTP.getId(); this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.connectionReuseStrategy = connectionReuseStrategy != null ? connectionReuseStrategy : DefaultConnectionReuseStrategy.INSTANCE; @@ -313,12 +315,13 @@ void terminateExchange(final HttpException ex) throws HttpException, IOException suspendSessionInput(); final ServerHttp1StreamHandler streamHandler; final HttpCoreContext context = HttpCoreContext.create(); - context.setAttribute(HttpCoreContext.SSL_SESSION, getSSLSession()); - context.setAttribute(HttpCoreContext.CONNECTION_ENDPOINT, getEndpointDetails()); + context.setSSLSession(getSSLSession()); + context.setEndpointDetails(getEndpointDetails()); if (outgoing == null) { streamHandler = new ServerHttp1StreamHandler( outputChannel, httpProcessor, + http1Config, connectionReuseStrategy, exchangeHandlerFactory, context); @@ -327,6 +330,7 @@ void terminateExchange(final HttpException ex) throws HttpException, IOException streamHandler = new ServerHttp1StreamHandler( new DelayedOutputChannel(outputChannel), httpProcessor, + http1Config, connectionReuseStrategy, exchangeHandlerFactory, context); @@ -343,12 +347,13 @@ void consumeHeader(final HttpRequest request, final EntityDetails entityDetails) } final ServerHttp1StreamHandler streamHandler; final HttpCoreContext context = HttpCoreContext.create(); - context.setAttribute(HttpCoreContext.SSL_SESSION, getSSLSession()); - context.setAttribute(HttpCoreContext.CONNECTION_ENDPOINT, getEndpointDetails()); + context.setSSLSession(getSSLSession()); + context.setEndpointDetails(getEndpointDetails()); if (outgoing == null) { streamHandler = new ServerHttp1StreamHandler( outputChannel, httpProcessor, + http1Config, connectionReuseStrategy, exchangeHandlerFactory, context); @@ -357,6 +362,7 @@ void consumeHeader(final HttpRequest request, final EntityDetails entityDetails) streamHandler = new ServerHttp1StreamHandler( new DelayedOutputChannel(outputChannel), httpProcessor, + http1Config, connectionReuseStrategy, exchangeHandlerFactory, context); @@ -477,6 +483,7 @@ private static class DelayedOutputChannel implements Http1StreamChannel channel) { this.channel = channel; @@ -492,13 +499,16 @@ public void submit( final HttpResponse response, final boolean endStream, final FlushMode flushMode) throws HttpException, IOException { - synchronized (this) { + lock.lock(); + try { if (direct) { channel.submit(response, endStream, flushMode); } else { delayedResponse = response; completed = endStream; } + } finally { + lock.unlock(); } } @@ -524,48 +534,63 @@ public void setSocketTimeout(final Timeout timeout) { @Override public int write(final ByteBuffer src) throws IOException { - synchronized (this) { + lock.lock(); + try { return direct ? channel.write(src) : 0; + } finally { + lock.unlock(); } } @Override public void complete(final List trailers) throws IOException { - synchronized (this) { + lock.lock(); + try { if (direct) { channel.complete(trailers); } else { completed = true; } + } finally { + lock.unlock(); } } @Override public boolean abortGracefully() throws IOException { - synchronized (this) { + lock.lock(); + try { if (direct) { return channel.abortGracefully(); } completed = true; return true; + } finally { + lock.unlock(); } } @Override public boolean isCompleted() { - synchronized (this) { + lock.lock(); + try { return direct ? channel.isCompleted() : completed; + } finally { + lock.unlock(); } } @Override public void activate() throws IOException, HttpException { - synchronized (this) { + lock.lock(); + try { direct = true; if (delayedResponse != null) { channel.submit(delayedResponse, completed, completed ? FlushMode.IMMEDIATE : FlushMode.BUFFER); delayedResponse = null; } + } finally { + lock.unlock(); } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamDuplexerFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamDuplexerFactory.java index 5d1c6464e8..7c078b6e7d 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamDuplexerFactory.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamDuplexerFactory.java @@ -85,9 +85,9 @@ public ServerHttp1StreamDuplexerFactory( this.connectionReuseStrategy = connectionReuseStrategy != null ? connectionReuseStrategy : DefaultConnectionReuseStrategy.INSTANCE; this.requestParserFactory = requestParserFactory != null ? requestParserFactory : - new DefaultHttpRequestParserFactory(http1Config); + new DefaultHttpRequestParserFactory(this.http1Config); this.responseWriterFactory = responseWriterFactory != null ? responseWriterFactory : - DefaultHttpResponseWriterFactory.INSTANCE; + new DefaultHttpResponseWriterFactory(this.http1Config); this.incomingContentStrategy = incomingContentStrategy != null ? incomingContentStrategy : DefaultContentLengthStrategy.INSTANCE; this.outgoingContentStrategy = outgoingContentStrategy != null ? outgoingContentStrategy : diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamHandler.java index 65b4e4cf5d..a03ce059ec 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamHandler.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamHandler.java @@ -46,6 +46,7 @@ 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; @@ -68,6 +69,7 @@ class ServerHttp1StreamHandler implements ResourceHolder { private final DataStreamChannel internalDataChannel; private final ResponseChannel responseChannel; private final HttpProcessor httpProcessor; + private final Http1Config http1Config; private final HandlerFactory exchangeHandlerFactory; private final ConnectionReuseStrategy connectionReuseStrategy; private final HttpCoreContext context; @@ -83,6 +85,7 @@ class ServerHttp1StreamHandler implements ResourceHolder { ServerHttp1StreamHandler( final Http1StreamChannel outputChannel, final HttpProcessor httpProcessor, + final Http1Config http1Config, final ConnectionReuseStrategy connectionReuseStrategy, final HandlerFactory exchangeHandlerFactory, final HttpCoreContext context) { @@ -125,7 +128,6 @@ public void sendInformation(final HttpResponse response, final HttpContext httpC @Override public void sendResponse( final HttpResponse response, final EntityDetails responseEntityDetails, final HttpContext httpContext) throws HttpException, IOException { - ServerSupport.validateResponse(response, responseEntityDetails); commitResponse(response, responseEntityDetails); } @@ -143,11 +145,12 @@ public String toString() { }; this.httpProcessor = httpProcessor; + this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT; this.connectionReuseStrategy = connectionReuseStrategy; this.exchangeHandlerFactory = exchangeHandlerFactory; this.context = context; - this.responseCommitted = new AtomicBoolean(false); - this.done = new AtomicBoolean(false); + this.responseCommitted = new AtomicBoolean(); + this.done = new AtomicBoolean(); this.keepAlive = true; this.requestState = MessageState.HEADERS; this.responseState = MessageState.IDLE; @@ -159,8 +162,11 @@ private void commitResponse( if (responseCommitted.compareAndSet(false, true)) { final ProtocolVersion transportVersion = response.getVersion(); - if (transportVersion != null && transportVersion.greaterEquals(HttpVersion.HTTP_2)) { - throw new UnsupportedHttpVersionException(transportVersion); + if (transportVersion != null) { + if (!transportVersion.lessEquals(http1Config.getVersion())) { + throw new UnsupportedHttpVersionException(transportVersion); + } + context.setProtocolVersion(transportVersion); } final int status = response.getCode(); @@ -168,8 +174,7 @@ private void commitResponse( throw new HttpException("Invalid response: " + status); } - context.setProtocolVersion(transportVersion != null ? transportVersion : HttpVersion.HTTP_1_1); - context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response); + context.setResponse(response); httpProcessor.process(response, responseEntityDetails, context); final boolean endStream = responseEntityDetails == null || @@ -245,29 +250,29 @@ void consumeHeader(final HttpRequest request, final EntityDetails requestEntityD receivedRequest = request; requestState = requestEntityDetails == null ? MessageState.COMPLETE : MessageState.BODY; - 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; - final ProtocolVersion transportVersion = request.getVersion(); if (transportVersion != null && transportVersion.greaterEquals(HttpVersion.HTTP_2)) { throw new UnsupportedHttpVersionException(transportVersion); } - context.setProtocolVersion(transportVersion != null ? transportVersion : HttpVersion.HTTP_1_1); - context.setAttribute(HttpCoreContext.HTTP_REQUEST, request); + context.setProtocolVersion(transportVersion != null ? transportVersion : http1Config.getVersion()); + context.setRequest(request); try { 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()) { @@ -292,7 +297,7 @@ boolean isOutputReady() { } } - void produceOutput() throws HttpException, IOException { + void produceOutput() throws IOException { switch (responseState) { case BODY: exchangeHandler.produce(internalDataChannel); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/routing/PathPatternMatcher.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/routing/PathPatternMatcher.java new file mode 100644 index 0000000000..5517f4e888 --- /dev/null +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/routing/PathPatternMatcher.java @@ -0,0 +1,65 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * 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("*")); + } + +} diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/HandlerEntry.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/routing/PathRoute.java similarity index 59% rename from httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/HandlerEntry.java rename to httpcore5/src/main/java/org/apache/hc/core5/http/impl/routing/PathRoute.java index 3033e4f395..b07086a627 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/HandlerEntry.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/routing/PathRoute.java @@ -24,31 +24,40 @@ * . * */ -package org.apache.hc.core5.http2.impl.nio.bootstrap; +package org.apache.hc.core5.http.impl.routing; -final class HandlerEntry { +import java.util.Objects; - final String hostname; - final String uriPattern; - final T handler; +import org.apache.hc.core5.annotation.Internal; +import org.apache.hc.core5.util.Args; - public HandlerEntry(final String hostname, final String uriPattern, final T handler) { - this.hostname = hostname; - this.uriPattern = uriPattern; - this.handler = handler; +@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() { - final StringBuilder builder = new StringBuilder(); - builder.append("HandlerEntry [hostname="); - builder.append(hostname); - builder.append(", uriPattern="); - builder.append(uriPattern); - builder.append(", handler="); - builder.append(handler); - builder.append("]"); - return builder.toString(); + return pattern + " -> " + handler; } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/routing/RequestRouter.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/routing/RequestRouter.java new file mode 100644 index 0000000000..b0d27c89da --- /dev/null +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/routing/RequestRouter.java @@ -0,0 +1,257 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * 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 class SingleAuthorityResolver implements Function { + + private final URIAuthority singleAuthority; + private final T router; + + SingleAuthorityResolver(final URIAuthority singleAuthority, final T router) { + this.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 class NoAuthorityResolver implements Function { + + @Override + public T apply(final URIAuthority authority) { + return 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 = new 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); + } + + } + +} diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/routing/UriPathRouter.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/routing/UriPathRouter.java new file mode 100644 index 0000000000..24a92d60e8 --- /dev/null +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/routing/UriPathRouter.java @@ -0,0 +1,154 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * 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<>(e -> e, new BestMatcher<>(), routes); + } + + static UriPathRouter ordered(final List> routes) { + return new UriPathRouter<>(e -> e, new OrderedMatcher<>(), routes); + } + + static UriPathRouter regEx(final List> routes) { + return new UriPathRouter<>(Pattern::compile, new RegexMatcher<>(), 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 *}
  • + *
+ */ + final static class BestMatcher implements BiFunction>, T> { + + @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 *}
  • + *
+ */ + final static class OrderedMatcher implements BiFunction>, T> { + + @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. + */ + final static class RegexMatcher implements BiFunction>, T> { + + @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; + } + + } + +} diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/BHttpConnection.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/BHttpConnection.java index c23af9b8ec..82a2b22498 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/BHttpConnection.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/BHttpConnection.java @@ -65,6 +65,7 @@ public interface BHttpConnection extends HttpConnection { * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/EofSensorInputStream.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/EofSensorInputStream.java index a8af3a401b..2da06ad069 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/EofSensorInputStream.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/EofSensorInputStream.java @@ -128,6 +128,9 @@ public int read() throws IOException { @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()) { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpClientConnection.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpClientConnection.java index e8cab9c527..9e8bf3acf7 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpClientConnection.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpClientConnection.java @@ -44,7 +44,7 @@ public interface HttpClientConnection extends BHttpConnection { /** * Checks whether this connection is in a consistent state. * - * @return {@code true} if the connection is known to be + * @return {@code false} if the connection is known to be * in a inconsistent state and cannot be re-used. * * @see #terminateRequest(ClassicHttpRequest) diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpClientResponseHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpClientResponseHandler.java index 397ef2e2f4..6cd6125d71 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpClientResponseHandler.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpClientResponseHandler.java @@ -35,7 +35,7 @@ * Handler that encapsulates the process of generating a response object * from a {@link ClassicHttpResponse}. * - * + * @param the type of the response. * @since 4.0 */ @FunctionalInterface @@ -49,6 +49,7 @@ public interface HttpClientResponseHandler { * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpConnectionFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpConnectionFactory.java index e1e68b02c2..8ecab21157 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpConnectionFactory.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpConnectionFactory.java @@ -30,15 +30,38 @@ 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); + } + } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpFilterChain.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpFilterChain.java index 0d81a6fc25..38dbf180a5 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpFilterChain.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpFilterChain.java @@ -53,6 +53,8 @@ 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; @@ -60,6 +62,8 @@ interface ResponseTrigger { * 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; @@ -71,6 +75,8 @@ interface ResponseTrigger { * @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, diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpFilterHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpFilterHandler.java index 10bec168f5..16d88cd460 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpFilterHandler.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpFilterHandler.java @@ -54,6 +54,8 @@ public interface HttpFilterHandler { * @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, diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageParser.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageParser.java index f9909fd7d3..6449927c6d 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageParser.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageParser.java @@ -36,8 +36,7 @@ /** * Message parser intended to build HTTP message head from an input stream. * - * @param - * {@link MessageHeaders} or a subclass + * @param The type of {@link MessageHeaders}. * * @since 4.0 */ diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageParserFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageParserFactory.java index 9522cea28a..b6a1396534 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageParserFactory.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageParserFactory.java @@ -33,10 +33,19 @@ /** * 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); + } + } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageWriter.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageWriter.java index 026a040fcc..ee193d9161 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageWriter.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageWriter.java @@ -36,6 +36,7 @@ /** * Message writer intended to serialize HTTP message head to an output stream. * + * @param The type of {@link MessageHeaders}. * @since 4.0 */ public interface HttpMessageWriter { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageWriterFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageWriterFactory.java index 544df602e7..83a7c41f1f 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageWriterFactory.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpMessageWriterFactory.java @@ -32,6 +32,7 @@ /** * Factory for {@link HttpMessageWriter} instances. * + * @param The type of {@link MessageHeaders}. * @since 4.3 */ public interface HttpMessageWriterFactory { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpServerRequestHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpServerRequestHandler.java index 8d07104dd5..23ef67b256 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpServerRequestHandler.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpServerRequestHandler.java @@ -58,6 +58,8 @@ 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; @@ -65,6 +67,8 @@ interface ResponseTrigger { * 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; @@ -76,6 +80,8 @@ interface ResponseTrigger { * @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, diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpTransportMetrics.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpTransportMetrics.java index 947d0d034a..a32ff0e994 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpTransportMetrics.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/HttpTransportMetrics.java @@ -35,7 +35,9 @@ public interface HttpTransportMetrics { /** - * Returns the number of bytes transferred. + * Gets the number of bytes transferred. + * + * @return the number of bytes transferred. */ long getBytesTransferred(); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/ResponseOutOfOrderStrategy.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/ResponseOutOfOrderStrategy.java index ff03f68b1f..135ebff742 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/ResponseOutOfOrderStrategy.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/ResponseOutOfOrderStrategy.java @@ -32,11 +32,13 @@ 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 java.net.SocketException} or {@link java.net.SocketTimeoutException} + * 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 @@ -45,9 +47,9 @@ public interface ResponseOutOfOrderStrategy { /** - * Called before each write to the to a socket {@link java.io.OutputStream} with the number of - * bytes that have already been sent, and the size of the write that will occur if this check - * does not encounter an out of order response. + * 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. diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/SessionInputBuffer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/SessionInputBuffer.java index 566c20f569..3dd0b58a25 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/SessionInputBuffer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/SessionInputBuffer.java @@ -76,6 +76,7 @@ public interface SessionInputBuffer { * {@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} diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/SessionOutputBuffer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/SessionOutputBuffer.java index 3245bd446c..03fdf8aae2 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/SessionOutputBuffer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/SessionOutputBuffer.java @@ -74,6 +74,7 @@ public interface SessionOutputBuffer { * @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; @@ -83,6 +84,7 @@ public interface SessionOutputBuffer { * 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; @@ -91,6 +93,7 @@ public interface SessionOutputBuffer { * 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; @@ -103,6 +106,7 @@ public interface SessionOutputBuffer { * 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; @@ -115,6 +119,7 @@ public interface SessionOutputBuffer { * 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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/SocketConfig.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/SocketConfig.java index 78fbb1321c..951f7e71ed 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/SocketConfig.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/SocketConfig.java @@ -28,6 +28,7 @@ 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; @@ -56,6 +57,9 @@ public class SocketConfig { 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( @@ -67,6 +71,9 @@ public class SocketConfig { 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; @@ -77,6 +84,9 @@ public class SocketConfig { this.sndBufSize = sndBufSize; this.rcvBufSize = rcvBufSize; this.backlogSize = backlogSize; + this.tcpKeepIdle = tcpKeepIdle; + this.tcpKeepInterval = tcpKeepInterval; + this.tcpKeepCount = tcpKeepCount; this.socksProxyAddress = socksProxyAddress; } @@ -139,6 +149,30 @@ 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) */ @@ -157,6 +191,9 @@ public String toString() { .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(); @@ -177,6 +214,9 @@ public static SocketConfig.Builder copy(final SocketConfig config) { .setSndBufSize(config.getSndBufSize()) .setRcvBufSize(config.getRcvBufSize()) .setBacklogSize(config.getBacklogSize()) + .setTcpKeepIdle(config.getTcpKeepIdle()) + .setTcpKeepInterval(config.getTcpKeepInterval()) + .setTcpKeepCount(config.getTcpKeepCount()) .setSocksProxyAddress(config.getSocksProxyAddress()); } @@ -190,6 +230,9 @@ public static class Builder { private int sndBufSize; private int rcvBufSize; private int backlogSize; + private int tcpKeepIdle; + private int tcpKeepInterval; + private int tcpKeepCount; private SocketAddress socksProxyAddress; Builder() { @@ -201,11 +244,16 @@ public static class Builder { this.sndBufSize = 0; this.rcvBufSize = 0; this.backlogSize = 0; + this.tcpKeepIdle = -1; + this.tcpKeepInterval = -1; + this.tcpKeepCount = -1; this.socksProxyAddress = null; } /** * @see #setSoTimeout(Timeout) + * + * @return this instance. */ public Builder setSoTimeout(final int soTimeout, final TimeUnit timeUnit) { this.soTimeout = Timeout.of(soTimeout, timeUnit); @@ -218,7 +266,7 @@ public Builder setSoTimeout(final int soTimeout, final TimeUnit timeUnit) { * Default: 3 minutes *

* - * @return the default socket timeout value for blocking I/O operations. + * @return this instance. * @see java.net.SocketOptions#SO_TIMEOUT */ public Builder setSoTimeout(final Timeout soTimeout) { @@ -227,13 +275,13 @@ public Builder setSoTimeout(final Timeout soTimeout) { } /** - * Determines the default value of the {@link java.net.SocketOptions#SO_REUSEADDR} parameter + * Determines the default value of the {@link SocketOptions#SO_REUSEADDR} parameter * for newly created sockets. *

* Default: {@code false} *

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

* Default: {@code -1} *

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

* Default: {@code false} *

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

* Default: {@code false} *

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

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

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

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

* - * @return the default value of the {@link java.net.SocketOptions#SO_RCVBUF} parameter. + * @return this instance. * @see java.net.SocketOptions#SO_RCVBUF * @since 4.4 */ @@ -332,7 +382,8 @@ public Builder setRcvBufSize(final int rcvBufSize) { *

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

- * @return the maximum queue length for incoming connection indications + * + * @return this instance. * @since 4.4 */ public Builder setBacklogSize(final int backlogSize) { @@ -340,8 +391,53 @@ public Builder setBacklogSize(final int backlogSize) { return this; } + /** + * Determines the time (in seconds) the connection needs to remain idle before TCP starts + * sending keepalive probes. + *

+ * Default: {@code -1} (system default) + *

+ * + * @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 -1} (system default) + *

+ * + * @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 -1} (system default) + *

+ * + * @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; @@ -350,10 +446,11 @@ public Builder setSocksProxyAddress(final SocketAddress socksProxyAddress) { public SocketConfig build() { return new SocketConfig( - Timeout.defaultsToDisabled(soTimeout), + Timeout.defaultsToInfinite(soTimeout), soReuseAddress, soLinger != null ? soLinger : TimeValue.NEG_ONE_SECOND, soKeepAlive, tcpNoDelay, sndBufSize, rcvBufSize, backlogSize, + tcpKeepIdle, tcpKeepInterval, tcpKeepCount, socksProxyAddress); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/AbstractHttpEntity.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/AbstractHttpEntity.java index c757e284ff..eb6d5ac7f3 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/AbstractHttpEntity.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/AbstractHttpEntity.java @@ -32,6 +32,7 @@ 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; @@ -43,6 +44,9 @@ /** * 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 */ @@ -54,26 +58,72 @@ public abstract class AbstractHttpEntity implements HttpEntity { 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 = contentType != null ? contentType.toString() : null; + 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"); @@ -88,6 +138,13 @@ public static void writeTo(final HttpEntity entity, final OutputStream outStream } } + /** + * 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); @@ -103,21 +160,45 @@ 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(); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/BasicHttpEntity.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/BasicHttpEntity.java index 730ac911ab..a2fd637511 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/BasicHttpEntity.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/BasicHttpEntity.java @@ -38,7 +38,12 @@ /** * 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) @@ -47,31 +52,110 @@ 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 length, final ContentType contentType, final String contentEncoding, + 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 = length; + 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 length, final ContentType contentType, final String contentEncoding) { - this(content, length, contentType, contentEncoding, false); + final InputStream content, final long contentLength, final ContentType contentType, final String contentEncoding) { + this(content, contentLength, contentType, contentEncoding, false); } - public BasicHttpEntity(final InputStream content, final long length, final ContentType contentType) { - this(content, length, contentType, null); + /** + * 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); } @@ -86,14 +170,26 @@ 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 this.content != null && this.content != EmptyInputStream.INSTANCE; + return true; } @Override diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/BufferedHttpEntity.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/BufferedHttpEntity.java index 05a7efdbea..e7c061bf4c 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/BufferedHttpEntity.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/BufferedHttpEntity.java @@ -54,6 +54,7 @@ public class BufferedHttpEntity extends HttpEntityWrapper { * * @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); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/ByteArrayEntity.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/ByteArrayEntity.java index 4c4b7efac9..9f5976420b 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/ByteArrayEntity.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/ByteArrayEntity.java @@ -39,77 +39,195 @@ /** * 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[] b; + 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[] b, final int off, final int len, final ContentType contentType, final String contentEncoding, + final byte[] buf, final int off, final int len, final ContentType contentType, final String contentEncoding, final boolean chunked) { super(contentType, contentEncoding, chunked); - Args.notNull(b, "Source byte array"); + Args.notNull(buf, "Source byte array"); Args.notNegative(off, "offset"); Args.notNegative(len, "length"); Args.notNegative(off + len, "off + len"); - Args.check(off <= b.length, "off %s cannot be greater then b.length %s ", off, b.length); - Args.check(off + len <= b.length, "off + len %s cannot be less then b.length %s ", off + len, b.length); - this.b = b; + 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[] b, final int off, final int len, final ContentType contentType, final String contentEncoding) { - this(b, off, len, contentType, contentEncoding, false); + 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[] b, final ContentType contentType, final String contentEncoding, final boolean chunked) { + final byte[] buf, final ContentType contentType, final String contentEncoding, final boolean chunked) { super(contentType, contentEncoding, chunked); - Args.notNull(b, "Source byte array"); - this.b = b; + Args.notNull(buf, "Source byte array"); + this.buf = buf; this.off = 0; - this.len = this.b.length; + 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[] b, final ContentType contentType, final String contentEncoding) { - this(b, contentType, contentEncoding, false); + public ByteArrayEntity(final byte[] buf, final ContentType contentType, final String contentEncoding) { + this(buf, contentType, contentEncoding, false); } - public ByteArrayEntity(final byte[] b, final ContentType contentType, final boolean chunked) { - this(b, contentType, null, chunked); + /** + * 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); } - public ByteArrayEntity(final byte[] b, final ContentType contentType) { - this(b, contentType, null, false); + /** + * 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[] b, final int off, final int len, final ContentType contentType, final boolean chunked) { - this(b, off, len, contentType, null, chunked); + final byte[] buf, final int off, final int len, final ContentType contentType, final boolean chunked) { + this(buf, off, len, contentType, null, chunked); } - public ByteArrayEntity(final byte[] b, final int off, final int len, final ContentType contentType) { - this(b, off, len, contentType, null, 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 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; @@ -122,21 +240,33 @@ public final long getContentLength() { @Override public final InputStream getContent() { - return new ByteArrayInputStream(this.b, this.off, this.len); + 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.b, this.off, this.len); + 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 { } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/ByteBufferEntity.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/ByteBufferEntity.java index 91c057fd40..f9f4b7560f 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/ByteBufferEntity.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/ByteBufferEntity.java @@ -42,6 +42,19 @@ 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"); @@ -49,10 +62,29 @@ public ByteBufferEntity(final ByteBuffer buffer, final ContentType contentType, 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; @@ -77,6 +109,9 @@ public int read() throws IOException { @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; } @@ -87,11 +122,23 @@ public int read(final byte[] bytes, final int off, final int len) throws IOExcep }; } + /** + * {@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 { } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/EmptyInputStream.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/EmptyInputStream.java index 294952bd98..e08faa20c5 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/EmptyInputStream.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/EmptyInputStream.java @@ -34,6 +34,9 @@ */ public final class EmptyInputStream extends InputStream { + /** + * Singleton instance of EmptyInputStream. + */ public static final EmptyInputStream INSTANCE = new EmptyInputStream(); private EmptyInputStream() { @@ -94,6 +97,9 @@ public int read(final byte[] buf) { */ @Override public int read(final byte[] buf, final int off, final int len) { + if (len == 0) { + return 0; + } return -1; } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/EntityTemplate.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/EntityTemplate.java index 2abfa1597d..448f3d8379 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/EntityTemplate.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/EntityTemplate.java @@ -42,6 +42,10 @@ /** * 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 */ @@ -51,6 +55,20 @@ 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) { @@ -71,6 +89,12 @@ public InputStream getContent() throws IOException { return new ByteArrayInputStream(buf.toByteArray()); } + /** + * {@inheritDoc} + *

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

+ */ @Override public boolean isRepeatable() { return true; @@ -82,11 +106,23 @@ public void writeTo(final OutputStream outStream) throws IOException { 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 { } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/EntityUtils.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/EntityUtils.java index c282815e2f..e4c3c000f7 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/EntityUtils.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/EntityUtils.java @@ -60,7 +60,7 @@ 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.ISO_8859_1; + 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; @@ -235,7 +235,7 @@ private static String toString(final HttpEntity entity, final ContentType conten /** * 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 "ISO-8859-1" is used. + * 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, @@ -256,7 +256,7 @@ public static String 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 "ISO-8859-1" is used. + * 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, @@ -295,7 +295,7 @@ public static String 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 "ISO-8859-1" is used. + * 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 @@ -315,7 +315,7 @@ public static String 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 "ISO-8859-1" is used. + * 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 @@ -337,7 +337,7 @@ public static String toString( /** * 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, "ISO-8859-1" is used. + * failing that, "UTF-8" is used. * * @param entity the entity to convert to a string; must not be null * @return String containing the content. @@ -354,7 +354,7 @@ public static String toString(final HttpEntity entity) throws IOException, Parse /** * 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, "ISO-8859-1" is used. + * failing that, "UTF-8" is used. * * @param entity the entity to convert to a string; must not be null * @param maxResultLength diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/FileEntity.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/FileEntity.java index 64d02e877c..c2c166f71c 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/FileEntity.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/FileEntity.java @@ -39,7 +39,10 @@ /** * 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) @@ -47,21 +50,59 @@ 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(); @@ -72,11 +113,23 @@ 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 diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/HttpEntities.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/HttpEntities.java index bcdbb982ec..508d89b7b3 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/HttpEntities.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/HttpEntities.java @@ -50,7 +50,7 @@ import org.apache.hc.core5.util.Args; /** - * {HttpEntity} factory methods. + * {@link HttpEntity} factory methods. * * @since 5.0 */ diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/InputStreamEntity.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/InputStreamEntity.java index 596632e70e..c580849fd1 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/InputStreamEntity.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/InputStreamEntity.java @@ -44,28 +44,78 @@ 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 inStream, final long length, final ContentType contentType, final String contentEncoding) { + final InputStream content, final long contentLength, final ContentType contentType, final String contentEncoding) { super(contentType, contentEncoding); - this.content = Args.notNull(inStream, "Source input stream"); - this.length = length; + this.content = Args.notNull(content, "Source input stream"); + this.length = contentLength; } - public InputStreamEntity(final InputStream inStream, final long length, final ContentType contentType) { - this(inStream, length, contentType, null); + /** + * 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); } - public InputStreamEntity(final InputStream inStream, final ContentType contentType) { - this(inStream, -1, 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; } /** - * @return the content length or {@code -1} if unknown + * {@inheritDoc} + * + * @return the content length or {@code -1} if unknown. */ @Override public final long getContentLength() { @@ -110,6 +160,12 @@ public final void writeTo(final OutputStream outStream) throws IOException { } } + /** + * {@inheritDoc} + *

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

+ */ @Override public final boolean isStreaming() { return true; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/PathEntity.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/PathEntity.java index 7bbd2049dd..7d10442eef 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/PathEntity.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/PathEntity.java @@ -39,22 +39,58 @@ /** * 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; @@ -74,11 +110,23 @@ 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 diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/SerializableEntity.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/SerializableEntity.java index e13a0715c0..f5ad6030ef 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/SerializableEntity.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/SerializableEntity.java @@ -42,6 +42,10 @@ /** * 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 */ @@ -52,6 +56,12 @@ public class SerializableEntity extends AbstractHttpEntity { /** * Creates new instance of this class. + *

+ * The new instance: + *

+ *
    + *
  • is not chunked.
  • + *
* * @param serializable the serializable object. * @param contentType the content type. @@ -65,6 +75,13 @@ public SerializableEntity( /** * 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. @@ -80,16 +97,34 @@ public final InputStream getContent() throws IOException, IllegalStateException 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; @@ -103,6 +138,12 @@ public final void writeTo(final OutputStream outStream) throws IOException { out.flush(); } + /** + * {@inheritDoc} + *

+ * This implementation is a no-op. + *

+ */ @Override public final void close() throws IOException { } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/StringEntity.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/StringEntity.java index 4dd22b5c81..ff3418815a 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/StringEntity.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/entity/StringEntity.java @@ -41,6 +41,10 @@ /** * 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 */ @@ -50,60 +54,119 @@ public class StringEntity extends AbstractHttpEntity { private final byte[] content; /** - * Creates a StringEntity with the specified content and content type. - * - * @param string content to be used. Not {@code null}. - * @param contentType content type to be used. May be {@code null}, in which case the default - * MIME type {@link ContentType#TEXT_PLAIN} is assumed. + * 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.ISO_8859_1); + 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); } /** - * Creates a StringEntity with the specified content and charset. The MIME type defaults - * to "text/plain". - * - * @param string content to be used. Not {@code null}. - * @param charset character set to be used. May be {@code null}, in which case the default - * is {@link StandardCharsets#ISO_8859_1} is assumed + * 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); } /** - * Creates a StringEntity with the specified content. The content type defaults to - * {@link ContentType#TEXT_PLAIN}. - * - * @param string content to be used. Not {@code null}. + * 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.
  • + *
* - * @throws IllegalArgumentException if the string parameter is null + * @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; @@ -126,11 +189,23 @@ public final void writeTo(final OutputStream outStream) throws IOException { 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 diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/ssl/DefaultTlsSetupHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/ssl/DefaultTlsSetupHandler.java index 2e926bc391..17d03a1c5a 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/ssl/DefaultTlsSetupHandler.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/ssl/DefaultTlsSetupHandler.java @@ -30,6 +30,7 @@ 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; @@ -40,10 +41,29 @@ */ 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); + } } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/AbstractHttpServerAuthFilter.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/AbstractHttpServerAuthFilter.java index 59e14ede09..9e7f257364 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/AbstractHttpServerAuthFilter.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/AbstractHttpServerAuthFilter.java @@ -70,6 +70,7 @@ protected AbstractHttpServerAuthFilter(final boolean respondImmediately) { * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/BasicHttpServerExpectationDecorator.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/BasicHttpServerExpectationDecorator.java index d45a79184b..0ed4961958 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/BasicHttpServerExpectationDecorator.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/BasicHttpServerExpectationDecorator.java @@ -31,13 +31,12 @@ 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.HttpException; -import org.apache.hc.core5.http.HttpHeaders; 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; @@ -74,8 +73,8 @@ public final void handle( final ClassicHttpRequest request, final ResponseTrigger responseTrigger, final HttpContext context) throws HttpException, IOException { - final Header expect = request.getFirstHeader(HttpHeaders.EXPECT); - if (expect != null && HeaderElements.CONTINUE.equalsIgnoreCase(expect.getValue())) { + 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)); @@ -83,6 +82,10 @@ public final void handle( 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); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/HttpServerExpectationFilter.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/HttpServerExpectationFilter.java index f499a12d22..307188c61e 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/HttpServerExpectationFilter.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/HttpServerExpectationFilter.java @@ -32,17 +32,16 @@ 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.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 @@ -60,6 +59,7 @@ public class HttpServerExpectationFilter implements HttpFilterHandler { * @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; @@ -72,6 +72,7 @@ protected boolean verify(final ClassicHttpRequest request, final HttpContext con * @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; @@ -83,9 +84,8 @@ public final void handle( final HttpFilterChain.ResponseTrigger responseTrigger, final HttpContext context, final HttpFilterChain chain) throws HttpException, IOException { - final Header expect = request.getFirstHeader(HttpHeaders.EXPECT); - final boolean expectContinue = expect != null && HeaderElements.CONTINUE.equalsIgnoreCase(expect.getValue()); - if (expectContinue) { + 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)); @@ -96,6 +96,12 @@ public final void handle( 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); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/message/AbstractHeaderElementIterator.java b/httpcore5/src/main/java/org/apache/hc/core5/http/message/AbstractHeaderElementIterator.java index 21ee047018..c497bc5932 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/message/AbstractHeaderElementIterator.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/message/AbstractHeaderElementIterator.java @@ -35,13 +35,13 @@ import org.apache.hc.core5.util.Args; /** - * {@link java.util.Iterator} of {@link org.apache.hc.core5.http.HeaderElement}s. + * {@link Iterator} of {@link org.apache.hc.core5.http.HeaderElement}s. * * @since 5.0 */ abstract class AbstractHeaderElementIterator implements Iterator { - private final Iterator
headerIt; + private final Iterator headerIt; private T currentElement; private CharSequence buffer; @@ -50,7 +50,7 @@ abstract class AbstractHeaderElementIterator implements Iterator { /** * Creates a new instance of BasicHeaderElementIterator */ - AbstractHeaderElementIterator(final Iterator
headerIterator) { + AbstractHeaderElementIterator(final Iterator headerIterator) { this.headerIt = Args.notNull(headerIterator, "Header iterator"); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderElement.java b/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderElement.java index d353247120..ad01d0bac9 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderElement.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderElement.java @@ -55,7 +55,7 @@ public class BasicHeaderElement implements HeaderElement { public BasicHeaderElement( final String name, final String value, - final NameValuePair[] parameters) { + final NameValuePair... parameters) { super(); this.name = Args.notNull(name, "Name"); this.value = value; @@ -73,7 +73,7 @@ public BasicHeaderElement( * @param value header element value. May be {@code null} */ public BasicHeaderElement(final String name, final String value) { - this(name, value, null); + this(name, value, (NameValuePair[]) null); } @Override diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderElementIterator.java b/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderElementIterator.java index 90d1e537b1..3bbdf55ee0 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderElementIterator.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderElementIterator.java @@ -34,7 +34,7 @@ import org.apache.hc.core5.util.Args; /** - * {@link java.util.Iterator} of {@link org.apache.hc.core5.http.HeaderElement}s. + * {@link Iterator} of {@link org.apache.hc.core5.http.HeaderElement}s. * * @since 4.0 */ @@ -46,13 +46,13 @@ public class BasicHeaderElementIterator extends AbstractHeaderElementIterator headerIterator, + final Iterator headerIterator, final HeaderValueParser parser) { super(headerIterator); this.parser = Args.notNull(parser, "Parser"); } - public BasicHeaderElementIterator(final Iterator
headerIterator) { + public BasicHeaderElementIterator(final Iterator headerIterator) { this(headerIterator, BasicHeaderValueParser.INSTANCE); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderIterator.java b/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderIterator.java index 3e11669ff0..fdaba87209 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderIterator.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderIterator.java @@ -34,7 +34,7 @@ import org.apache.hc.core5.util.Args; /** - * {@link java.util.Iterator} of {@link org.apache.hc.core5.http.Header}s. + * {@link Iterator} of {@link org.apache.hc.core5.http.Header}s. * * @since 4.0 */ diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderValueParser.java b/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderValueParser.java index 5399e529d8..d21b061e59 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderValueParser.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHeaderValueParser.java @@ -28,7 +28,6 @@ package org.apache.hc.core5.http.message; import java.util.ArrayList; -import java.util.BitSet; import java.util.List; import org.apache.hc.core5.annotation.Contract; @@ -51,10 +50,8 @@ public class BasicHeaderValueParser implements HeaderValueParser { private final static char PARAM_DELIMITER = ';'; private final static char ELEM_DELIMITER = ','; - // IMPORTANT! - // These private static variables must be treated as immutable and never exposed outside this class - private static final BitSet TOKEN_DELIMITER = Tokenizer.INIT_BITSET('=', PARAM_DELIMITER, ELEM_DELIMITER); - private static final BitSet VALUE_DELIMITER = Tokenizer.INIT_BITSET(PARAM_DELIMITER, 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; @@ -93,7 +90,10 @@ public HeaderElement parseHeaderElement(final CharSequence buffer, final ParserC final NameValuePair nvp = parseNameValuePair(buffer, cursor); NameValuePair[] params = null; if (!cursor.atEnd()) { - final char ch = buffer.charAt(cursor.getPos() - 1); + 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); } @@ -110,9 +110,14 @@ public NameValuePair[] parseParameters(final CharSequence buffer, final ParserCu while (!cursor.atEnd()) { final NameValuePair param = parseNameValuePair(buffer, cursor); params.add(param); - final char ch = buffer.charAt(cursor.getPos() - 1); - if (ch == ELEM_DELIMITER) { - break; + 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); @@ -127,15 +132,12 @@ public NameValuePair parseNameValuePair(final CharSequence buffer, final ParserC if (cursor.atEnd()) { return new BasicNameValuePair(name, null); } - final int delim = buffer.charAt(cursor.getPos()); - cursor.updatePos(cursor.getPos() + 1); + 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); - if (!cursor.atEnd()) { - cursor.updatePos(cursor.getPos() + 1); - } return new BasicNameValuePair(name, value); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHttpRequest.java b/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHttpRequest.java index 0d4279e115..5fd6b2985b 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHttpRequest.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicHttpRequest.java @@ -208,9 +208,6 @@ public String getPath() { @Override public void setPath(final String path) { - if (path != null) { - Args.check(!path.startsWith("//"), "URI path begins with multiple slashes"); - } this.path = path; this.requestUri = null; } @@ -238,11 +235,12 @@ public void setAuthority(final URIAuthority authority) { } /** - * Sets a flag that the {@link #getRequestUri()} method should return the request URI - * in an absolute form. + * 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) { @@ -255,9 +253,8 @@ public String getRequestUri() { final StringBuilder buf = new StringBuilder(); assembleRequestUri(buf); return buf.toString(); - } else { - return getPath(); } + return getPath(); } @Override @@ -278,7 +275,6 @@ public void setUri(final URI requestUri) { final StringBuilder buf = new StringBuilder(); final String rawPath = requestUri.getRawPath(); if (!TextUtils.isBlank(rawPath)) { - Args.check(!rawPath.startsWith("//"), "URI path begins with multiple slashes"); buf.append(rawPath); } else { buf.append("/"); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicLineParser.java b/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicLineParser.java index 04dd847c18..003fd218c1 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicLineParser.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicLineParser.java @@ -27,14 +27,13 @@ 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.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; @@ -50,11 +49,8 @@ public class BasicLineParser implements LineParser { public final static BasicLineParser INSTANCE = new BasicLineParser(); - // IMPORTANT! - // These private static variables must be treated as immutable and never exposed outside this class - private static final BitSet FULL_STOP = Tokenizer.INIT_BITSET('.'); - private static final BitSet BLANKS = Tokenizer.INIT_BITSET(' ', '\t'); - private static final BitSet COLON = Tokenizer.INIT_BITSET(':'); + private static final Tokenizer.Delimiter BLANKS = Tokenizer.delimiters(' ', '\t'); + private static final Tokenizer.Delimiter COLON = Tokenizer.delimiters(':'); /** * A version of the protocol to parse. @@ -112,29 +108,7 @@ ProtocolVersion parseProtocolVersion( } cursor.updatePos(pos + protolength + 1); - - final String token1 = this.tokenizer.parseToken(buffer, cursor, FULL_STOP); - final int major; - try { - major = Integer.parseInt(token1); - } catch (final NumberFormatException e) { - throw new ParseException("Invalid protocol major version number", - buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos()); - } - if (cursor.atEnd()) { - throw new ParseException("Invalid protocol version", - buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos()); - } - cursor.updatePos(cursor.getPos() + 1); - final String token2 = this.tokenizer.parseToken(buffer, cursor, BLANKS); - final int minor; - try { - minor = Integer.parseInt(token2); - } catch (final NumberFormatException e) { - throw new ParseException("Invalid protocol minor version number", - buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos()); - } - return HttpVersion.get(major, minor); + return ProtocolVersionParser.INSTANCE.parse(protoname, HttpVersion::get, buffer, cursor, null); } /** @@ -180,20 +154,31 @@ public StatusLine parseStatusLine(final CharArrayBuffer buffer) throws ParseExce this.tokenizer.skipWhiteSpace(buffer, cursor); final ProtocolVersion ver = parseProtocolVersion(buffer, cursor); this.tokenizer.skipWhiteSpace(buffer, cursor); - final String s = this.tokenizer.parseToken(buffer, cursor, BLANKS); - for (int i = 0; i < s.length(); i++) { - if (!Character.isDigit(s.charAt(i))) { + 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); } - final int statusCode; - try { - statusCode = Integer.parseInt(s); - } catch (final NumberFormatException e) { - throw new ParseException("Status line contains invalid status code", - buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos()); + 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); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicListHeaderIterator.java b/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicListHeaderIterator.java index 295b6b605b..8f11f7cb82 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicListHeaderIterator.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicListHeaderIterator.java @@ -36,7 +36,7 @@ import org.apache.hc.core5.util.Asserts; /** - * {@link java.util.Iterator} of {@link org.apache.hc.core5.http.Header}s. For use by {@link HeaderGroup}. + * {@link Iterator} of {@link org.apache.hc.core5.http.Header}s. For use by {@link HeaderGroup}. * * @since 4.0 */ @@ -46,7 +46,7 @@ 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; + private final List allHeaders; /** * The position of the next header in {@link #allHeaders allHeaders}. @@ -73,7 +73,7 @@ class BasicListHeaderIterator implements Iterator
{ * @param name the name of the headers over which to iterate, or * {@code null} for any */ - public BasicListHeaderIterator(final List
headers, final String name) { + public BasicListHeaderIterator(final List headers, final String name) { super(); this.allHeaders = Args.notNull(headers, "Header list"); this.headerName = name; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicTokenIterator.java b/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicTokenIterator.java index 45ca98d572..d42e823184 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicTokenIterator.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/message/BasicTokenIterator.java @@ -27,7 +27,6 @@ package org.apache.hc.core5.http.message; -import java.util.BitSet; import java.util.Iterator; import org.apache.hc.core5.http.Header; @@ -35,13 +34,13 @@ import org.apache.hc.core5.util.Tokenizer; /** - * {@link java.util.Iterator} of {@link org.apache.hc.core5.http.Header} tokens.. + * {@link Iterator} of {@link org.apache.hc.core5.http.Header} tokens.. * * @since 4.0 */ public class BasicTokenIterator extends AbstractHeaderElementIterator { - private static final BitSet COMMA = Tokenizer.INIT_BITSET(','); + private static final Tokenizer.Delimiter COMMA = Tokenizer.delimiters(','); private final Tokenizer tokenizer; @@ -50,7 +49,7 @@ public class BasicTokenIterator extends AbstractHeaderElementIterator { * * @param headerIterator the iterator for the headers to tokenize */ - public BasicTokenIterator(final Iterator
headerIterator) { + public BasicTokenIterator(final Iterator headerIterator) { super(headerIterator); this.tokenizer = Tokenizer.INSTANCE; } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/message/BufferedHeader.java b/httpcore5/src/main/java/org/apache/hc/core5/http/message/BufferedHeader.java index 6148e09d97..8e73ecf9c0 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/message/BufferedHeader.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/message/BufferedHeader.java @@ -29,6 +29,7 @@ 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; @@ -60,6 +61,8 @@ public class BufferedHeader implements FormattedHeader, Serializable { */ private final int valuePos; + private String value; + /** * @since 5.0 */ @@ -110,7 +113,27 @@ public String getName() { @Override public String getValue() { - return this.buffer.substringTrimmed(this.valuePos, this.buffer.length()); + 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 diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/message/MessageSupport.java b/httpcore5/src/main/java/org/apache/hc/core5/http/message/MessageSupport.java index aad640e030..b0c6a446bc 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/message/MessageSupport.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/message/MessageSupport.java @@ -27,24 +27,30 @@ package org.apache.hc.core5.http.message; -import java.util.BitSet; +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.Consumer; 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.TextUtils; import org.apache.hc.core5.util.Tokenizer; /** @@ -54,19 +60,20 @@ */ public class MessageSupport { - /** - * An empty immutable {@code String} array. - */ - private static final String[] EMPTY_STRING_ARRAY = {}; - private MessageSupport() { // Do not allow utility class to be instantiated. } - public static void formatTokens(final CharArrayBuffer dst, final String... tokens) { + /** + * @since 5.3 + */ + public static void formatTokens(final CharArrayBuffer dst, final List tokens) { Args.notNull(dst, "Destination"); - for (int i = 0; i < tokens.length; i++) { - final String element = tokens[i]; + if (tokens == null) { + return; + } + for (int i = 0; i < tokens.size(); i++) { + final String element = tokens.get(i); if (i > 0) { dst.append(", "); } @@ -74,17 +81,47 @@ public static void formatTokens(final CharArrayBuffer dst, final String... token } } + 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 || tokens.isEmpty()) { + if (tokens == null) { return; } - formatTokens(dst, tokens.toArray(EMPTY_STRING_ARRAY)); + 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.3 + */ + public static Header headerOfTokens(final String name, final List tokens) { Args.notBlank(name, "Header name"); - if (tokens == null || tokens.isEmpty()) { + if (tokens == null) { return null; } final CharArrayBuffer buffer = new CharArrayBuffer(256); @@ -94,9 +131,12 @@ public static Header format(final String name, final Set tokens) { return BufferedHeader.create(buffer); } - public static Header format(final String name, final String... tokens) { + /** + * @since 5.3 + */ + public static Header header(final String name, final Set tokens) { Args.notBlank(name, "Header name"); - if (tokens == null || tokens.length == 0) { + if (tokens == null) { return null; } final CharArrayBuffer buffer = new CharArrayBuffer(256); @@ -106,36 +146,275 @@ public static Header format(final String name, final String... tokens) { return BufferedHeader.create(buffer); } - private static final BitSet COMMA = Tokenizer.INIT_BITSET(','); + 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); + } - public static Set parseTokens(final CharSequence src, final ParserCursor cursor) { + /** + * @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.3 + */ + public static void parseTokens(final CharSequence src, final ParserCursor cursor, final Consumer consumer) { Args.notNull(src, "Source"); Args.notNull(cursor, "Cursor"); - final Set tokens = new LinkedHashSet<>(); + Args.notNull(consumer, "Consumer"); while (!cursor.atEnd()) { final int pos = cursor.getPos(); if (src.charAt(pos) == ',') { cursor.updatePos(pos + 1); } final String token = Tokenizer.INSTANCE.parseToken(src, cursor, COMMA); - if (!TextUtils.isBlank(token)) { - tokens.add(token); - } + consumer.accept(token); + } + } + + /** + * @since 5.3 + */ + public static void parseTokens(final Header header, final Consumer 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()); + parseTokens(buf, cursor, consumer); + } else { + final String value = header.getValue(); + final ParserCursor cursor = new ParserCursor(0, value.length()); + parseTokens(value, cursor, consumer); } + } + + /** + * @since 5.3 + */ + public static void parseTokens(final MessageHeaders headers, final String headerName, final Consumer consumer) { + Args.notNull(headers, "Headers"); + final Iterator
it = headers.headerIterator(headerName); + while (it.hasNext()) { + parseTokens(it.next(), 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) { + Args.notNull(buffer, "Char sequence"); + Args.notNull(cursor, "Parser cursor"); + Args.notNull(consumer, "Consumer"); + while (!cursor.atEnd()) { + final HeaderElement element = BasicHeaderValueParser.INSTANCE.parseHeaderElement(buffer, cursor); + consumer.accept(element); + if (!cursor.atEnd()) { + final char ch = buffer.charAt(cursor.getPos()); + if (ch == ',') { + cursor.updatePos(cursor.getPos() + 1); + } + } + } + } + + /** + * @since 5.3 + */ + public static void parseElements(final Header header, final Consumer 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()); - return parseTokens(buf, cursor); + parseElements(buf, cursor, consumer); + } else { + final String value = header.getValue(); + final ParserCursor cursor = new ParserCursor(0, value.length()); + parseElements(value, cursor, consumer); + } + } + + /** + * @since 5.3 + */ + public static void parseElements(final MessageHeaders headers, final String headerName, final Consumer consumer) { + Args.notNull(headers, "Headers"); + final Iterator
it = headers.headerIterator(headerName); + while (it.hasNext()) { + parseElements(it.next(), 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; + } + } } - final String value = header.getValue(); - final ParserCursor cursor = new ParserCursor(0, value.length()); - return parseTokens(value, cursor); } public static void addContentTypeHeader(final HttpMessage message, final EntityDetails entity) { @@ -154,27 +433,11 @@ public static void addTrailerHeader(final HttpMessage message, final EntityDetai if (entity != null && !message.containsHeader(HttpHeaders.TRAILER)) { final Set trailerNames = entity.getTrailerNames(); if (trailerNames != null && !trailerNames.isEmpty()) { - message.setHeader(MessageSupport.format(HttpHeaders.TRAILER, trailerNames)); + message.setHeader(MessageSupport.header(HttpHeaders.TRAILER, trailerNames)); } } } - 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)); - } - - public static HeaderElement[] parse(final Header header) { - Args.notNull(header, "Headers"); - final String value = header.getValue(); - if (value == null) { - return new HeaderElement[] {}; - } - final ParserCursor cursor = new ParserCursor(0, value.length()); - return BasicHeaderValueParser.INSTANCE.parseElements(value, cursor); - } - /** * @since 5.0 */ @@ -191,4 +454,49 @@ public static boolean canResponseHaveBody(final String method, final HttpRespons && status != HttpStatus.SC_NOT_MODIFIED; } + 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; + } + } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/message/StatusLine.java b/httpcore5/src/main/java/org/apache/hc/core5/http/message/StatusLine.java index 9eb52f4ba4..1e16135998 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/message/StatusLine.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/message/StatusLine.java @@ -102,6 +102,7 @@ public StatusClass getStatusClass() { /** * 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() { @@ -111,6 +112,7 @@ public boolean isInformational() { /** * 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() { @@ -120,6 +122,7 @@ public boolean isSuccessful() { /** * 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() { @@ -129,6 +132,7 @@ public boolean isRedirection() { /** * 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() { @@ -138,6 +142,7 @@ public boolean isClientError() { /** * 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() { @@ -148,6 +153,7 @@ public boolean isServerError() { * 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() { @@ -174,6 +180,7 @@ public String toString() { /** * Standard classes of HTTP status codes, plus {@code OTHER} for non-standard codes. + * @see org.apache.hc.core5.http.HttpStatus */ public enum StatusClass { @@ -212,6 +219,7 @@ public enum StatusClass { * * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncClientExchangeHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncClientExchangeHandler.java index 75f8c56b1b..257212a209 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncClientExchangeHandler.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncClientExchangeHandler.java @@ -49,6 +49,8 @@ public interface AsyncClientExchangeHandler extends AsyncDataExchangeHandler { * * @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; @@ -59,6 +61,8 @@ public interface AsyncClientExchangeHandler extends AsyncDataExchangeHandler { * @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; @@ -67,6 +71,8 @@ public interface AsyncClientExchangeHandler extends AsyncDataExchangeHandler { * * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncDataConsumer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncDataConsumer.java index 6c280512f1..2440f46215 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncDataConsumer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncDataConsumer.java @@ -46,6 +46,7 @@ public interface AsyncDataConsumer extends ResourceHolder { * 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; @@ -58,6 +59,7 @@ public interface AsyncDataConsumer extends ResourceHolder { * information on the capacity channel. * * @param src data source. + * @throws IOException in case of an I/O error. */ void consume(ByteBuffer src) throws IOException; @@ -65,6 +67,8 @@ public interface AsyncDataConsumer extends ResourceHolder { * 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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncDataProducer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncDataProducer.java index 59656369a2..55ab17a171 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncDataProducer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncDataProducer.java @@ -39,8 +39,18 @@ 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(); @@ -48,8 +58,13 @@ public interface AsyncDataProducer extends ResourceHolder { * 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 to accepting more data. + * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncEntityConsumer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncEntityConsumer.java index b276e39e9a..af1cdb5d81 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncEntityConsumer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncEntityConsumer.java @@ -46,6 +46,8 @@ public interface AsyncEntityConsumer extends AsyncDataConsumer { * * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncFilterChain.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncFilterChain.java index 33a892a6ca..af8e828820 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncFilterChain.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncFilterChain.java @@ -54,6 +54,8 @@ 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; @@ -61,6 +63,8 @@ interface ResponseTrigger { * 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; @@ -69,6 +73,8 @@ interface ResponseTrigger { * * @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; @@ -82,6 +88,8 @@ interface ResponseTrigger { * 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, diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncFilterHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncFilterHandler.java index 4e6dfaea49..fdf1764f2b 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncFilterHandler.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncFilterHandler.java @@ -61,6 +61,8 @@ public interface AsyncFilterHandler { * @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, diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncPushConsumer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncPushConsumer.java index beaf393bad..d74bac25e5 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncPushConsumer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncPushConsumer.java @@ -50,6 +50,8 @@ public interface AsyncPushConsumer extends AsyncDataConsumer { * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncPushProducer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncPushProducer.java index b7e4fd5aec..6b7daa3698 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncPushProducer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncPushProducer.java @@ -47,6 +47,8 @@ public interface AsyncPushProducer extends AsyncDataProducer { * * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncRequestConsumer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncRequestConsumer.java index 89f58a4ba8..7903ab7909 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncRequestConsumer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncRequestConsumer.java @@ -37,7 +37,7 @@ /** * Abstract asynchronous request consumer. * - * @param request representation. + * @param the future result type returned for callbacks. * * @since 5.0 */ @@ -52,6 +52,8 @@ public interface AsyncRequestConsumer extends AsyncDataConsumer { * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncRequestProducer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncRequestProducer.java index 8839adc981..8fd10929ae 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncRequestProducer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncRequestProducer.java @@ -46,11 +46,13 @@ public interface AsyncRequestProducer extends AsyncDataProducer { * * @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; /** - * Determines whether the producer can consistently produce the same content + * Tests whether the producer can consistently produce the same content * after invocation of {@link ResourceHolder#releaseResources()}. */ boolean isRepeatable(); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncResponseConsumer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncResponseConsumer.java index e5a0be66c5..7b88e03df3 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncResponseConsumer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncResponseConsumer.java @@ -52,6 +52,8 @@ public interface AsyncResponseConsumer extends AsyncDataConsumer { * @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; @@ -61,6 +63,8 @@ void consumeResponse(HttpResponse response, EntityDetails entityDetails, HttpCon * * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncResponseProducer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncResponseProducer.java index 3423e9ba35..2c54c660ec 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncResponseProducer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncResponseProducer.java @@ -46,6 +46,8 @@ public interface AsyncResponseProducer extends AsyncDataProducer { * * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncServerExchangeHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncServerExchangeHandler.java index 6c738adae8..d66899ea25 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncServerExchangeHandler.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncServerExchangeHandler.java @@ -51,6 +51,8 @@ public interface AsyncServerExchangeHandler extends AsyncDataExchangeHandler { * 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, diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncServerRequestHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncServerRequestHandler.java index dce7c966f3..d529dd0454 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncServerRequestHandler.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncServerRequestHandler.java @@ -62,6 +62,8 @@ interface ResponseTrigger { * * @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; @@ -70,6 +72,8 @@ interface ResponseTrigger { * * @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; @@ -79,6 +83,8 @@ interface ResponseTrigger { * @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; @@ -94,6 +100,7 @@ interface ResponseTrigger { * 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; @@ -105,6 +112,8 @@ interface ResponseTrigger { * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/CapacityChannel.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/CapacityChannel.java index 9d73d9e957..1c59386a01 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/CapacityChannel.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/CapacityChannel.java @@ -49,6 +49,7 @@ public interface CapacityChannel { * * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/DataStreamChannel.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/DataStreamChannel.java index da0f74ac44..b956d7baad 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/DataStreamChannel.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/DataStreamChannel.java @@ -36,7 +36,7 @@ import org.apache.hc.core5.http.Header; /** - * Abstract byte stream channel + * Abstract byte stream channel. *

* Implementations are expected to be thread-safe. *

@@ -74,6 +74,8 @@ public interface DataStreamChannel extends StreamChannel { * 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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/HandlerFactory.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/HandlerFactory.java index 97b9131b07..a02fd35061 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/HandlerFactory.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/HandlerFactory.java @@ -46,6 +46,7 @@ public interface HandlerFactory { * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/NHttpMessageWriter.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/NHttpMessageWriter.java index 55fa563031..434339ae31 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/NHttpMessageWriter.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/NHttpMessageWriter.java @@ -35,6 +35,7 @@ /** * Message writer intended to serialize HTTP message head to a session buffer. * + * @param The type of {@link MessageHeaders}. * @since 4.0 */ public interface NHttpMessageWriter { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/RequestChannel.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/RequestChannel.java index 5ac4e405ad..f5a6dc8bf7 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/RequestChannel.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/RequestChannel.java @@ -52,6 +52,8 @@ public interface RequestChannel { * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ResponseChannel.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ResponseChannel.java index 930f20eaec..155af155ba 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ResponseChannel.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ResponseChannel.java @@ -53,6 +53,8 @@ public interface ResponseChannel { * * @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; @@ -63,6 +65,8 @@ public interface ResponseChannel { * @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; @@ -73,6 +77,8 @@ public interface ResponseChannel { * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/SessionOutputBuffer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/SessionOutputBuffer.java index 54b073a6a1..d0da941970 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/SessionOutputBuffer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/SessionOutputBuffer.java @@ -92,6 +92,7 @@ int flush(WritableByteChannel channel) * 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; @@ -105,6 +106,7 @@ void write(ReadableByteChannel src) * specific implementations of this interface. * * @param src the source buffer. + * @throws CharacterCodingException when a character encoding error occurs. */ void writeLine(CharArrayBuffer src) throws CharacterCodingException; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/StreamChannel.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/StreamChannel.java index 49fc1fdef1..dcf26d4e04 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/StreamChannel.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/StreamChannel.java @@ -51,12 +51,15 @@ public interface StreamChannel { * * @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; } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractBinAsyncEntityConsumer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractBinAsyncEntityConsumer.java index 4e292090be..a40aa64581 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractBinAsyncEntityConsumer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractBinAsyncEntityConsumer.java @@ -53,6 +53,8 @@ public abstract class AbstractBinAsyncEntityConsumer extends AbstractBinDataC * 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; @@ -60,6 +62,7 @@ public abstract class AbstractBinAsyncEntityConsumer extends AbstractBinDataC * Triggered to generate entity representation. * * @return the entity content + * @throws IOException in case of an I/O error. */ protected abstract T generateContent() throws IOException; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractBinAsyncEntityProducer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractBinAsyncEntityProducer.java index f8cd055888..b228d33977 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractBinAsyncEntityProducer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractBinAsyncEntityProducer.java @@ -28,7 +28,10 @@ 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; @@ -52,6 +55,8 @@ enum State { ACTIVE, FLUSHING, END_STREAM } private final ByteBuffer byteBuffer; private final ContentType contentType; + private final ReentrantLock lock; + private volatile State state; public AbstractBinAsyncEntityProducer(final int fragmentSizeHint, final ContentType contentType) { @@ -59,6 +64,7 @@ public AbstractBinAsyncEntityProducer(final int fragmentSizeHint, final ContentT 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 { @@ -130,12 +136,13 @@ final void streamEnd(final StreamChannel channel) throws IOException * {@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 contentType != null ? contentType.toString() : null; + return Objects.toString(contentType, null); } @Override @@ -150,7 +157,7 @@ public boolean isChunked() { @Override public Set getTrailerNames() { - return null; + return Collections.emptySet(); } @Override @@ -162,31 +169,40 @@ public long getContentLength() { public final int available() { if (state == State.ACTIVE) { return availableData(); - } else { - synchronized (byteBuffer) { - return byteBuffer.position(); - } + } + lock.lock(); + try { + return byteBuffer.position(); + } finally { + lock.unlock(); } } @Override public final void produce(final DataStreamChannel channel) throws IOException { - synchronized (byteBuffer) { + lock.lock(); + try { if (state == State.ACTIVE) { produceData(new StreamChannel() { @Override public int write(final ByteBuffer src) throws IOException { Args.notNull(src, "Buffer"); - synchronized (byteBuffer) { + lock.lock(); + try { return writeData(channel, src); + } finally { + lock.unlock(); } } @Override public void endStream() throws IOException { - synchronized (byteBuffer) { + lock.lock(); + try { streamEnd(channel); + } finally { + lock.unlock(); } } @@ -199,6 +215,8 @@ public void endStream() throws IOException { channel.endStream(); } } + } finally { + lock.unlock(); } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractBinDataConsumer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractBinDataConsumer.java index 628c9cce87..9529f5af0e 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractBinDataConsumer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractBinDataConsumer.java @@ -56,12 +56,14 @@ public abstract class AbstractBinDataConsumer implements AsyncDataConsumer { * * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractCharAsyncEntityConsumer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractCharAsyncEntityConsumer.java index c8ed00ae25..03c79588b7 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractCharAsyncEntityConsumer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractCharAsyncEntityConsumer.java @@ -61,6 +61,8 @@ 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; @@ -68,6 +70,7 @@ public AbstractCharAsyncEntityConsumer() { * Triggered to generate entity representation. * * @return the entity content + * @throws IOException in case of an I/O error. */ protected abstract T generateContent() throws IOException; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractCharAsyncEntityProducer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractCharAsyncEntityProducer.java index 38a93ed3ff..282ae9e602 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractCharAsyncEntityProducer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractCharAsyncEntityProducer.java @@ -33,7 +33,9 @@ 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; @@ -59,6 +61,7 @@ enum State { ACTIVE, FLUSHING, END_STREAM } private final int fragmentSizeHint; private final ContentType contentType; private final CharsetEncoder charsetEncoder; + private final ReentrantLock lock; private volatile State state; @@ -70,9 +73,10 @@ public AbstractCharAsyncEntityProducer( this.fragmentSizeHint = fragmentSizeHint >= 0 ? fragmentSizeHint : 0; this.bytebuf = ByteBuffer.allocate(bufferSize); this.contentType = contentType; - final Charset charset = ContentType.getCharset(contentType, StandardCharsets.US_ASCII); + 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 { @@ -145,12 +149,13 @@ final void streamEnd(final StreamChannel channel) throws IOException * {@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 contentType != null ? contentType.toString() : null; + return Objects.toString(contentType, null); } @Override @@ -177,31 +182,40 @@ public Set getTrailerNames() { public final int available() { if (state == State.ACTIVE) { return availableData(); - } else { - synchronized (bytebuf) { - return bytebuf.position(); - } + } + lock.lock(); + try { + return bytebuf.position(); + } finally { + lock.unlock(); } } @Override public final void produce(final DataStreamChannel channel) throws IOException { - synchronized (bytebuf) { + lock.lock(); + try { if (state == State.ACTIVE) { produceData(new StreamChannel() { @Override public int write(final CharBuffer src) throws IOException { Args.notNull(src, "Buffer"); - synchronized (bytebuf) { + lock.lock(); + try { return writeData(channel, src); + } finally { + lock.unlock(); } } @Override public void endStream() throws IOException { - synchronized (bytebuf) { + lock.lock(); + try { streamEnd(channel); + } finally { + lock.unlock(); } } @@ -223,6 +237,8 @@ public void endStream() throws IOException { } + } finally { + lock.unlock(); } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractCharDataConsumer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractCharDataConsumer.java index 74a2c4c3ac..fe3ab312f7 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractCharDataConsumer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AbstractCharDataConsumer.java @@ -79,12 +79,14 @@ public AbstractCharDataConsumer() { * * @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; @@ -111,15 +113,17 @@ private void doDecode(final boolean endOfStream) throws IOException { } 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.US_ASCII; + charset = StandardCharsets.UTF_8; } charsetDecoder = charset.newDecoder(); + this.charsetDecoder = charsetDecoder; if (charCodingConfig.getMalformedInputAction() != null) { charsetDecoder.onMalformedInput(charCodingConfig.getMalformedInputAction()); } @@ -134,6 +138,7 @@ private CharsetDecoder getCharsetDecoder() { 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(); @@ -159,6 +164,7 @@ public final void consume(final ByteBuffer src) throws IOException { // 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); } @@ -176,4 +182,4 @@ public final void streamEnd(final List trailers) throws HttpEx completed(); } -} \ No newline at end of file +} diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AsyncEntityProducers.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AsyncEntityProducers.java index 44a7246f3e..a7f156f399 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AsyncEntityProducers.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/AsyncEntityProducers.java @@ -222,6 +222,7 @@ public static AsyncEntityProducer create(final File content, final ContentType c } /** + * @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 { @@ -229,6 +230,7 @@ public static AsyncEntityProducer create(final Path content, final ContentType c } /** + * @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 { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/BasicAsyncEntityProducer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/BasicAsyncEntityProducer.java index 0a2bb6dedf..86502f5b5c 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/BasicAsyncEntityProducer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/BasicAsyncEntityProducer.java @@ -31,6 +31,8 @@ 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.AtomicReference; @@ -73,7 +75,7 @@ public BasicAsyncEntityProducer(final byte[] content) { 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.US_ASCII); + final Charset charset = ContentType.getCharset(contentType, StandardCharsets.UTF_8); this.bytebuf = charset.encode(CharBuffer.wrap(content)); this.length = this.bytebuf.remaining(); this.chunked = chunked; @@ -95,7 +97,7 @@ public boolean isRepeatable() { @Override public final String getContentType() { - return contentType != null ? contentType.toString() : null; + return Objects.toString(contentType, null); } @Override @@ -120,7 +122,7 @@ public boolean isChunked() { @Override public Set getTrailerNames() { - return null; + return Collections.emptySet(); } @Override diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/DigestingEntityConsumer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/DigestingEntityConsumer.java index db1b245936..12a2bfc8df 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/DigestingEntityConsumer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/DigestingEntityConsumer.java @@ -46,6 +46,7 @@ * 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 { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/DiscardingEntityConsumer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/DiscardingEntityConsumer.java index 97746c3262..e1d5d625d8 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/DiscardingEntityConsumer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/DiscardingEntityConsumer.java @@ -40,6 +40,7 @@ /** * No-op {@link AsyncEntityConsumer} that discards all data from the data stream. * + * @param entity representation. * @since 5.2 */ public final class DiscardingEntityConsumer implements AsyncEntityConsumer { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/FileEntityProducer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/FileEntityProducer.java index 2ac1c00c1d..e56e90ff85 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/FileEntityProducer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/FileEntityProducer.java @@ -30,6 +30,8 @@ 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; @@ -86,7 +88,7 @@ public boolean isRepeatable() { @Override public String getContentType() { - return contentType != null ? contentType.toString() : null; + return Objects.toString(contentType, null); } @Override @@ -111,7 +113,7 @@ public boolean isChunked() { @Override public Set getTrailerNames() { - return null; + return Collections.emptySet(); } @Override diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/PathEntityProducer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/PathEntityProducer.java index 0e283dbbc3..7a35d3ca73 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/PathEntityProducer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/entity/PathEntityProducer.java @@ -32,6 +32,8 @@ 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; @@ -110,7 +112,7 @@ public long getContentLength() { @Override public String getContentType() { - return contentType != null ? contentType.toString() : null; + return Objects.toString(contentType, null); } public Exception getException() { @@ -119,7 +121,7 @@ public Exception getException() { @Override public Set getTrailerNames() { - return null; + return Collections.emptySet(); } @Override diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/BasicClientTlsStrategy.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/BasicClientTlsStrategy.java index 1ba874fdd8..e9f97ebdaa 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/BasicClientTlsStrategy.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/BasicClientTlsStrategy.java @@ -30,6 +30,7 @@ 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; @@ -105,8 +106,21 @@ public void upgrade( final Object attachment, final Timeout handshakeTimeout, final FutureCallback callback) { - tlsSession.startTls(sslContext, endpoint, sslBufferMode, - TlsSupport.enforceStrongSecurity(initializer), verifier, handshakeTimeout, 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); } /** diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/TlsSupport.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/TlsSupport.java index 779f695be8..e04c11a836 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/TlsSupport.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ssl/TlsSupport.java @@ -40,12 +40,18 @@ */ 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) -> { - final SSLParameters sslParameters = sslEngine.getSSLParameters(); - sslParameters.setProtocols(TLS.excludeWeak(sslParameters.getProtocols())); - sslParameters.setCipherSuites(TlsCiphers.excludeWeak(sslParameters.getCipherSuites())); - sslEngine.setSSLParameters(sslParameters); + sslEngine.setSSLParameters(enforceStrongSecurity(sslEngine.getSSLParameters())); if (initializer != null) { initializer.initialize(endpoint, sslEngine); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractAsyncPushHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractAsyncPushHandler.java index 78a0fcdba2..f46dd201f6 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractAsyncPushHandler.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractAsyncPushHandler.java @@ -62,6 +62,8 @@ public AbstractAsyncPushHandler(final AsyncResponseConsumer responseConsumer) * * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractAsyncServerAuthFilter.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractAsyncServerAuthFilter.java index b05669232e..0a15831220 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractAsyncServerAuthFilter.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractAsyncServerAuthFilter.java @@ -74,6 +74,7 @@ protected AbstractAsyncServerAuthFilter(final boolean respondImmediately) { * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractServerExchangeHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractServerExchangeHandler.java index 9607ae96dc..da217b0528 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractServerExchangeHandler.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractServerExchangeHandler.java @@ -52,6 +52,7 @@ /** * Abstract server side message exchange handler. * + * @param the type of request messages. * @since 5.0 */ public abstract class AbstractServerExchangeHandler implements AsyncServerExchangeHandler { @@ -70,6 +71,7 @@ public AbstractServerExchangeHandler() { * @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, @@ -85,6 +87,8 @@ protected abstract AsyncRequestConsumer supplyConsumer( * @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, diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncServerExpectationFilter.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncServerExpectationFilter.java index bfa05a2b00..e11893f58b 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncServerExpectationFilter.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncServerExpectationFilter.java @@ -31,10 +31,7 @@ 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; @@ -43,6 +40,8 @@ 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; /** @@ -66,19 +65,22 @@ public final AsyncDataConsumer handle( final HttpContext context, final AsyncFilterChain.ResponseTrigger responseTrigger, final AsyncFilterChain chain) throws HttpException, IOException { - if (entityDetails != null) { - final Header h = request.getFirstHeader(HttpHeaders.EXPECT); - if (h != null && HeaderElements.CONTINUE.equalsIgnoreCase(h.getValue())) { - 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; - } + 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); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicClientExchangeHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicClientExchangeHandler.java index 3296afcd93..467b9d637b 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicClientExchangeHandler.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicClientExchangeHandler.java @@ -51,6 +51,7 @@ * 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 { @@ -67,9 +68,9 @@ public BasicClientExchangeHandler( final FutureCallback resultCallback) { this.requestProducer = Args.notNull(requestProducer, "Request producer"); this.responseConsumer = Args.notNull(responseConsumer, "Response consumer"); - this.completed = new AtomicBoolean(false); + this.completed = new AtomicBoolean(); this.resultCallback = resultCallback; - this.outputTerminated = new AtomicBoolean(false); + this.outputTerminated = new AtomicBoolean(); } @Override diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicRequestConsumer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicRequestConsumer.java index 3567c18b56..c1c8e53e54 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicRequestConsumer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicRequestConsumer.java @@ -49,6 +49,7 @@ * 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> { @@ -83,17 +84,15 @@ public void consumeRequest( @Override public void completed(final T body) { - final Message result = new Message<>(request, body); if (resultCallback != null) { - resultCallback.completed(result); + resultCallback.completed(new Message<>(request, body)); } } }); } else { - final Message result = new Message<>(request, null); if (resultCallback != null) { - resultCallback.completed(result); + resultCallback.completed(new Message<>(request)); } } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicResponseConsumer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicResponseConsumer.java index be95f814cb..6b9e67dc68 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicResponseConsumer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicResponseConsumer.java @@ -50,6 +50,7 @@ * 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> { @@ -83,17 +84,15 @@ public void consumeResponse( @Override public void completed(final T body) { - final Message result = new Message<>(response, body); if (resultCallback != null) { - resultCallback.completed(result); + resultCallback.completed(new Message<>(response, body)); } } }); } else { - final Message result = new Message<>(response, null); if (resultCallback != null) { - resultCallback.completed(result); + resultCallback.completed(new Message<>(response)); } } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicServerExchangeHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicServerExchangeHandler.java index 38aba92a80..c4143823f5 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicServerExchangeHandler.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicServerExchangeHandler.java @@ -40,6 +40,7 @@ * 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 { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/AbstractClassicEntityConsumer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/AbstractClassicEntityConsumer.java index fbc9fd4504..b8122dbd97 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/AbstractClassicEntityConsumer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/AbstractClassicEntityConsumer.java @@ -77,6 +77,7 @@ public AbstractClassicEntityConsumer(final int initialBufferSize, final Executor * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/AbstractClassicEntityProducer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/AbstractClassicEntityProducer.java index 4b33b0b450..7e38b60d12 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/AbstractClassicEntityProducer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/AbstractClassicEntityProducer.java @@ -28,6 +28,7 @@ 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; @@ -67,6 +68,7 @@ public AbstractClassicEntityProducer(final int initialBufferSize, final ContentT * * @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; @@ -104,7 +106,7 @@ public final long getContentLength() { @Override public final String getContentType() { - return contentType != null ? contentType.toString() : null; + return Objects.toString(contentType, null); } @Override diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/AbstractClassicServerExchangeHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/AbstractClassicServerExchangeHandler.java index 3ab6b94ba7..421e46d307 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/AbstractClassicServerExchangeHandler.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/AbstractClassicServerExchangeHandler.java @@ -30,6 +30,7 @@ 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; @@ -92,6 +93,8 @@ public AbstractClassicServerExchangeHandler(final int initialBufferSize, final E * @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, @@ -108,7 +111,7 @@ public final void handleRequest( final EntityDetails entityDetails, final ResponseChannel responseChannel, final HttpContext context) throws HttpException, IOException { - final AtomicBoolean responseCommitted = new AtomicBoolean(false); + final AtomicBoolean responseCommitted = new AtomicBoolean(); final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK); final HttpResponse responseWrapper = new HttpResponseWrapper(response){ @@ -195,7 +198,7 @@ public boolean isChunked() { @Override public Set getTrailerNames() { - return null; + return Collections.emptySet(); } }, context); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ContentInputBuffer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ContentInputBuffer.java index 2871d5e3c2..72f60d1e71 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ContentInputBuffer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ContentInputBuffer.java @@ -78,6 +78,7 @@ public interface ContentInputBuffer { * {@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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ContentInputStream.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ContentInputStream.java index f3e567b95d..0df79a5dc0 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ContentInputStream.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/ContentInputStream.java @@ -54,6 +54,9 @@ public int available() throws IOException { @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); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/SharedInputBuffer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/SharedInputBuffer.java index 45dea032c5..cb3d30245e 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/SharedInputBuffer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/classic/SharedInputBuffer.java @@ -133,6 +133,9 @@ public int read() throws IOException { @Override public int read(final byte[] b, final int off, final int len) throws IOException { + if (len == 0) { + return 0; + } lock.lock(); try { setOutputMode(); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/BasicHttpContext.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/BasicHttpContext.java index b980de8bd6..0db1b180cf 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/BasicHttpContext.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/BasicHttpContext.java @@ -42,8 +42,9 @@ * Please note instances of this class can be thread unsafe if the * parent context is not thread safe. * - * @since 4.0 + * @deprecated Do not use. */ +@Deprecated @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL) public class BasicHttpContext implements HttpContext { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ForwardedRequest.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ForwardedRequest.java new file mode 100644 index 0000000000..e9ef4985eb --- /dev/null +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ForwardedRequest.java @@ -0,0 +1,186 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * 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); + } + +} diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/HttpCoreContext.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/HttpCoreContext.java index 6d25f77813..3097b3d33a 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/HttpCoreContext.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/HttpCoreContext.java @@ -27,52 +27,61 @@ package org.apache.hc.core5.http.protocol; -import java.util.Objects; +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; /** - * Implementation of {@link HttpContext} that provides convenience - * setters for user assignable attributes and getter for readable attributes. + * 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 { /** - * Attribute name of a {@link EndpointDetails} object that - * represents the actual connection endpoint details. + * @deprecated Use getter methods */ + @Deprecated public static final String CONNECTION_ENDPOINT = HttpContext.RESERVED_PREFIX + "connection-endpoint"; /** - * Attribute name of a {@link SSLSession} object that - * represents the actual connection endpoint details. + * @deprecated Use getter methods */ + @Deprecated public static final String SSL_SESSION = HttpContext.RESERVED_PREFIX + "ssl-session"; /** - * Attribute name of a {@link HttpRequest} object that - * represents the actual HTTP request. + * @deprecated Use getter methods */ + @Deprecated public static final String HTTP_REQUEST = HttpContext.RESERVED_PREFIX + "request"; /** - * Attribute name of a {@link HttpResponse} object that - * represents the actual HTTP response. + * @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(); @@ -83,24 +92,57 @@ public static HttpCoreContext adapt(final HttpContext context) { return new HttpCoreContext(context); } - private final HttpContext 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 context) { + public HttpCoreContext(final HttpContext parentContext) { super(); - this.context = Objects.requireNonNull(context); + this.parentContext = parentContext; } public HttpCoreContext() { super(); - this.context = new BasicHttpContext(); + 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.context.getProtocolVersion(); + return this.version != null ? this.version : HttpVersion.HTTP_1_1; } /** @@ -108,58 +150,220 @@ public ProtocolVersion getProtocolVersion() { */ @Override public void setProtocolVersion(final ProtocolVersion version) { - this.context.setProtocolVersion(version); + this.version = version; } @Override public Object getAttribute(final String id) { - return context.getAttribute(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) { - return context.setAttribute(id, obj); + if (map == null) { + map = new HashMap<>(); + } + return map.put(id, obj); } @Override public Object removeAttribute(final String id) { - return context.removeAttribute(id); + return map != null ? map.remove(id) : null; } - public T getAttribute(final String attribname, final Class clazz) { + public T getAttribute(final String id, final Class clazz) { Args.notNull(clazz, "Attribute class"); - final Object obj = getAttribute(attribname); - if (obj == null) { - return null; - } - return clazz.cast(obj); + final Object obj = getAttribute(id); + return obj != null ? clazz.cast(obj) : null ; } /** - * @since 5.0 + * Represents current request message head. + *

+ * This context attribute is expected to be populated by the protocol handler + * in the course of request execution. */ - public SSLSession getSSLSession() { - return getAttribute(SSL_SESSION, SSLSession.class); + 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 getAttribute(CONNECTION_ENDPOINT, EndpointDetails.class); + return endpointDetails; } - public HttpRequest getRequest() { - return getAttribute(HTTP_REQUEST, HttpRequest.class); + /** + * @since 5.3 + */ + @Internal + public void setEndpointDetails(final EndpointDetails endpointDetails) { + this.endpointDetails = endpointDetails; } - public HttpResponse getResponse() { - return getAttribute(HTTP_RESPONSE, HttpResponse.class); + /** + * 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 context.toString(); + return "HttpCoreContext{" + + "version=" + version + + ", request=" + request + + ", response=" + response + + ", endpointDetails=" + endpointDetails + + ", sslSession=" + sslSession + + '}'; } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/HttpDateGenerator.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/HttpDateGenerator.java index 8bfd090469..469dd17a4d 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/HttpDateGenerator.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/HttpDateGenerator.java @@ -32,6 +32,7 @@ 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; @@ -46,8 +47,12 @@ public class HttpDateGenerator { private static final int GRANULARITY_MILLIS = 1000; - /** Date format pattern used to generate the header in RFC 1123 format. */ + /** + * @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. @@ -59,22 +64,14 @@ public class HttpDateGenerator { public static final ZoneId GMT_ID = ZoneId.of("GMT"); /** Singleton instance. */ - public static final HttpDateGenerator INSTANCE = new HttpDateGenerator(PATTERN_RFC1123, GMT_ID); + 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; - HttpDateGenerator() { - dateTimeFormatter =new DateTimeFormatterBuilder() - .parseLenient() - .parseCaseInsensitive() - .appendPattern(PATTERN_RFC1123) - .toFormatter(); - zoneId = GMT_ID; - - } + private final ReentrantLock lock; private HttpDateGenerator(final String pattern, final ZoneId zoneId) { dateTimeFormatter = new DateTimeFormatterBuilder() @@ -83,16 +80,22 @@ private HttpDateGenerator(final String pattern, final ZoneId zoneId) { .appendPattern(pattern) .toFormatter(); this.zoneId = zoneId; + this.lock = new ReentrantLock(); } - public synchronized String getCurrentDate() { - final long now = System.currentTimeMillis(); - if (now - this.dateAsMillis > GRANULARITY_MILLIS) { - // Generate new date string - dateAsText = dateTimeFormatter.format(Instant.now().atZone(zoneId)); - dateAsMillis = now; + 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(); } - return dateAsText; } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/HttpProcessor.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/HttpProcessor.java index b5f84fa66f..e4f688eb7f 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/HttpProcessor.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/HttpProcessor.java @@ -33,7 +33,7 @@ import org.apache.hc.core5.http.HttpResponseInterceptor; /** - * HTTP protocol processor is a collection of protocol interceptors that + * 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. diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/LookupRegistry.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/LookupRegistry.java index 2bddaf2ec5..668e9c5269 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/LookupRegistry.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/LookupRegistry.java @@ -30,7 +30,11 @@ * 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 { /** diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestConformance.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestConformance.java new file mode 100644 index 0000000000..fd931b28d8 --- /dev/null +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestConformance.java @@ -0,0 +1,88 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * 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"); + } + } + +} diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestConnControl.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestConnControl.java index 2a1d77a837..bcab78f787 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestConnControl.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestConnControl.java @@ -41,10 +41,13 @@ import org.apache.hc.core5.util.Args; /** - * RequestConnControl is responsible for adding {@code Connection} header - * to the outgoing requests, which is essential for managing persistence of - * {@code HTTP/1.0} connections. This interceptor is recommended for - * client side protocol processors. + * 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 */ diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestContent.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestContent.java index 9151fd4865..7654b8985b 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestContent.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestContent.java @@ -32,6 +32,7 @@ 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; @@ -45,12 +46,13 @@ import org.apache.hc.core5.util.Args; /** - * RequestContent is the most important interceptor for outgoing requests. - * It is responsible for delimiting content length by adding - * {@code Content-Length} or {@code Transfer-Content} headers based + * 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 required for correct functioning of client side protocol - * processors. + *

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

* * @since 4.0 */ @@ -109,7 +111,15 @@ public void process(final HttpRequest request, final EntityDetails entity, final 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) { @@ -126,5 +136,33 @@ public void process(final HttpRequest request, final EntityDetails entity, final MessageSupport.addContentEncodingHeader(request, entity); } } - + private boolean isContentEnclosingMethod(final String method) { + return (Method.POST.isSame(method)||Method.PUT.isSame(method)||Method.PATCH.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"); + } + } + } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestDate.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestDate.java index 4111f8a510..88136b3bb6 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestDate.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestDate.java @@ -39,9 +39,12 @@ import org.apache.hc.core5.util.Args; /** - * RequestDate interceptor is responsible for adding {@code Date} header - * to the outgoing requests This interceptor is optional for client side - * protocol processors. + * 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 */ diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestExpectContinue.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestExpectContinue.java index 124a125f6c..62704a86db 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestExpectContinue.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestExpectContinue.java @@ -42,9 +42,12 @@ import org.apache.hc.core5.util.Args; /** - * RequestExpectContinue is responsible for enabling the 'expect-continue' - * handshake by adding {@code Expect} header. This interceptor is - * recommended for client side protocol processors. + * 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 */ diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestHandlerRegistry.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestHandlerRegistry.java index deabcd13de..76cfb3a27c 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestHandlerRegistry.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestHandlerRegistry.java @@ -46,8 +46,11 @@ * @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"; @@ -65,8 +68,23 @@ public RequestHandlerRegistry(final String canonicalHostName, final Supplier(); } + 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, () -> UriPatternType.newMatcher(patternType)); + this(canonicalHostName, () -> newMatcher(patternType)); } public RequestHandlerRegistry(final UriPatternType patternType) { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestTargetHost.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestTargetHost.java index 833898a1be..dfb71b22d3 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestTargetHost.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestTargetHost.java @@ -44,9 +44,12 @@ import org.apache.hc.core5.util.Args; /** - * RequestHostOutgoing is responsible for adding {@code Host} header to the outgoing message. - * This interceptor is required for client side protocol processors. - * + * 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) @@ -62,6 +65,7 @@ public RequestTargetHost() { super(); } + @SuppressWarnings("deprecation") @Override public void process(final HttpRequest request, final EntityDetails entity, final HttpContext context) throws HttpException, IOException { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestUserAgent.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestUserAgent.java index 96f6746834..0ce04efaf6 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestUserAgent.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestUserAgent.java @@ -39,8 +39,11 @@ import org.apache.hc.core5.util.Args; /** - * RequestUserAgent is responsible for adding {@code User-Agent} header. - * This interceptor is recommended for client side protocol processors. + * 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 */ diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestValidateHost.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestValidateHost.java index 1cf2d1c69b..3b4b0ef80e 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestValidateHost.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestValidateHost.java @@ -45,15 +45,20 @@ import org.apache.hc.core5.util.Args; /** - * RequestTargetHost is responsible for copying {@code Host} header value to - * {@link HttpRequest#setAuthority(URIAuthority)} of the incoming message. - * This interceptor is required for server side protocol processors. + * 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(); } @@ -63,6 +68,11 @@ public void process(final HttpRequest request, final EntityDetails entity, final 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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseConformance.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseConformance.java new file mode 100644 index 0000000000..f0d3b5168c --- /dev/null +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseConformance.java @@ -0,0 +1,75 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * 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"); + } + } + } + +} diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseConnControl.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseConnControl.java index f26c4732d3..0ca45f4f4f 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseConnControl.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseConnControl.java @@ -47,26 +47,32 @@ import org.apache.hc.core5.util.Args; /** - * ResponseConnControl is responsible for adding {@code Connection} header - * to the outgoing responses, which is essential for managing persistence of - * {@code HTTP/1.0} connections. This interceptor is recommended for - * server side protocol processors. + * 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 context) + public void process(final HttpResponse response, final EntityDetails entity, final HttpContext localContext) throws HttpException, IOException { Args.notNull(response, "HTTP response"); - Args.notNull(context, "HTTP context"); + 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 || @@ -86,8 +92,7 @@ public void process(final HttpResponse response, final EntityDetails entity, fin if (entity != null && entity.getContentLength() < 0 && ver.lessEquals(HttpVersion.HTTP_1_0)) { response.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); } else { - final HttpCoreContext coreContext = HttpCoreContext.adapt(context); - final HttpRequest request = coreContext.getRequest(); + final HttpRequest request = context.getRequest(); boolean closeRequested = false; boolean keepAliveRequested = false; if (request != null) { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseContent.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseContent.java index ba7083a49c..ac980c16ad 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseContent.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseContent.java @@ -45,18 +45,21 @@ import org.apache.hc.core5.util.Args; /** - * ResponseContent is the most important interceptor for outgoing responses. - * It is responsible for delimiting content length by adding - * {@code Content-Length} or {@code Transfer-Content} headers based + * 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 required for correct functioning of server side protocol - * processors. + *

+ * 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; /** diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseDate.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseDate.java index 05998fcc30..9102864820 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseDate.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseDate.java @@ -40,15 +40,20 @@ import org.apache.hc.core5.util.Args; /** - * ResponseDate is responsible for adding {@code Date} header to the - * outgoing responses. This interceptor is recommended for server side protocol - * processors. + * 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. + *

* * @since 4.0 */ @Contract(threading = ThreadingBehavior.SAFE) public class ResponseDate implements HttpResponseInterceptor { + public static final ResponseDate INSTANCE = new ResponseDate(); + public ResponseDate() { super(); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseServer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseServer.java index b01e15515d..de10ffbb52 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseServer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ResponseServer.java @@ -39,8 +39,11 @@ import org.apache.hc.core5.util.Args; /** - * ResponseServer is responsible for adding {@code Server} header. This - * interceptor is recommended for server side protocol processors. + * 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 */ diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriPatternMatcher.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriPatternMatcher.java index 97d8c8f925..ac8ab6d099 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriPatternMatcher.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriPatternMatcher.java @@ -32,9 +32,11 @@ 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; /** @@ -54,15 +56,22 @@ * * @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(); } /** @@ -73,8 +82,13 @@ public UriPatternMatcher() { * @see Map#entrySet() * @since 4.4.9 */ - public synchronized Set> entrySet() { - return new HashSet<>(map.entrySet()); + public Set> entrySet() { + lock.lock(); + try { + return new HashSet<>(map.entrySet()); + } finally { + lock.unlock(); + } } /** @@ -86,9 +100,14 @@ public synchronized Set> entrySet() { * the object. */ @Override - public synchronized void register(final String pattern, final T obj) { - Args.notNull(pattern, "URI request pattern"); - this.map.put(pattern, obj); + 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(); + } } /** @@ -98,11 +117,16 @@ public synchronized void register(final String pattern, final T obj) { * the pattern to unregister. */ @Override - public synchronized void unregister(final String pattern) { - if (pattern == null) { - return; + public void unregister(final String pattern) { + lock.lock(); + try { + if (pattern == null) { + return; + } + this.map.remove(pattern); + } finally { + lock.unlock(); } - this.map.remove(pattern); } /** @@ -113,25 +137,30 @@ public synchronized void unregister(final String pattern) { * @return object or {@code null} if no match is found. */ @Override - public synchronized T lookup(final String path) { - 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; + 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(); } - return obj; } /** @@ -144,11 +173,7 @@ public synchronized T lookup(final String path) { * @return {@code true} if the request URI matches the pattern, {@code false} otherwise. */ protected boolean matchUriRequestPattern(final String pattern, final String path) { - if (pattern.equals("*")) { - return true; - } - return (pattern.endsWith("*") && path.startsWith(pattern.substring(0, pattern.length() - 1))) - || (pattern.startsWith("*") && path.endsWith(pattern.substring(1))); + return PathPatternMatcher.INSTANCE.match(pattern, path); } @Override diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriPatternOrderedMatcher.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriPatternOrderedMatcher.java index 15fb5cde73..fdd9206dab 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriPatternOrderedMatcher.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriPatternOrderedMatcher.java @@ -32,9 +32,11 @@ 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; /** @@ -54,12 +56,18 @@ * * @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<>(); @@ -73,8 +81,13 @@ public UriPatternOrderedMatcher() { * @see Map#entrySet() * @since 4.4.9 */ - public synchronized Set> entrySet() { - return new HashSet<>(map.entrySet()); + public Set> entrySet() { + lock.lock(); + try { + return new HashSet<>(map.entrySet()); + } finally { + lock.unlock(); + } } /** @@ -84,9 +97,14 @@ public synchronized Set> entrySet() { * @param obj the object. */ @Override - public synchronized void register(final String pattern, final T obj) { - Args.notNull(pattern, "URI request pattern"); - this.map.put(pattern, obj); + 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(); + } } /** @@ -95,11 +113,16 @@ public synchronized void register(final String pattern, final T obj) { * @param pattern the pattern to unregister. */ @Override - public synchronized void unregister(final String pattern) { - if (pattern == null) { - return; + public void unregister(final String pattern) { + lock.lock(); + try { + if (pattern == null) { + return; + } + this.map.remove(pattern); + } finally { + lock.unlock(); } - this.map.remove(pattern); } /** @@ -109,18 +132,23 @@ public synchronized void unregister(final String pattern) { * @return object or {@code null} if no match is found. */ @Override - public synchronized T lookup(final String path) { - 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); + 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(); } - return null; } /** @@ -132,11 +160,7 @@ public synchronized T lookup(final String path) { * otherwise. */ protected boolean matchUriRequestPattern(final String pattern, final String path) { - if (pattern.equals("*")) { - return true; - } - return (pattern.endsWith("*") && path.startsWith(pattern.substring(0, pattern.length() - 1))) - || (pattern.startsWith("*") && path.endsWith(pattern.substring(1))); + return PathPatternMatcher.INSTANCE.match(pattern, path); } @Override diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriPatternType.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriPatternType.java index e906eb47c5..600f25d6a5 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriPatternType.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriPatternType.java @@ -34,6 +34,10 @@ 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<>(); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriRegexMatcher.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriRegexMatcher.java index 92f9beaed8..2756ecf7bc 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriRegexMatcher.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/UriRegexMatcher.java @@ -30,6 +30,7 @@ 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; @@ -46,17 +47,24 @@ * * @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(); } /** @@ -68,10 +76,15 @@ public UriRegexMatcher() { * the object. */ @Override - public synchronized void register(final String regex, final T obj) { - Args.notNull(regex, "URI request regex"); - this.objectMap.put(regex, obj); - this.patternMap.put(regex, Pattern.compile(regex)); + 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(); + } } /** @@ -81,12 +94,17 @@ public synchronized void register(final String regex, final T obj) { * the regex to unregister. */ @Override - public synchronized void unregister(final String regex) { - if (regex == null) { - return; + public void unregister(final String regex) { + lock.lock(); + try { + if (regex == null) { + return; + } + this.objectMap.remove(regex); + this.patternMap.remove(regex); + } finally { + lock.unlock(); } - this.objectMap.remove(regex); - this.patternMap.remove(regex); } /** @@ -97,19 +115,24 @@ public synchronized void unregister(final String regex) { * @return object or {@code null} if no match is found. */ @Override - public synchronized T lookup(final String path) { - 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()); + 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(); } - return obj; } @Override diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ViaRequest.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ViaRequest.java new file mode 100644 index 0000000000..811f3091a4 --- /dev/null +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/ViaRequest.java @@ -0,0 +1,127 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * 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); + } + } +} + diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/ssl/TLS.java b/httpcore5/src/main/java/org/apache/hc/core5/http/ssl/TLS.java index d1884f146b..8268eb5b53 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/ssl/TLS.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/ssl/TLS.java @@ -30,8 +30,10 @@ 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; /** @@ -90,12 +92,41 @@ 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()); - return TlsVersionParser.INSTANCE.parse(s, cursor, null); + 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) { @@ -115,8 +146,9 @@ public static String[] excludeWeak(final String... protocols) { } /** - * Check if a given protocol is considered secure and is enabled by default. + * 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 diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/ssl/TlsVersionParser.java b/httpcore5/src/main/java/org/apache/hc/core5/http/ssl/TlsVersionParser.java deleted file mode 100644 index 6ad359cfff..0000000000 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/ssl/TlsVersionParser.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package org.apache.hc.core5.http.ssl; - -import java.util.BitSet; - -import org.apache.hc.core5.http.ParseException; -import org.apache.hc.core5.http.ProtocolVersion; -import org.apache.hc.core5.util.Tokenizer; - -final class TlsVersionParser { - - public final static TlsVersionParser INSTANCE = new TlsVersionParser(); - - private final Tokenizer tokenizer; - - TlsVersionParser() { - this.tokenizer = Tokenizer.INSTANCE; - } - - ProtocolVersion parse( - final CharSequence buffer, - final Tokenizer.Cursor cursor, - final BitSet delimiters) 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); - } - final String s = this.tokenizer.parseToken(buffer, cursor, delimiters); - final int idx = s.indexOf('.'); - if (idx == -1) { - final int major; - try { - major = Integer.parseInt(s); - } catch (final NumberFormatException e) { - throw new ParseException("Invalid TLS major version", buffer, lowerBound, upperBound, pos); - } - return new ProtocolVersion("TLS", major, 0); - } else { - final String s1 = s.substring(0, idx); - final int major; - try { - major = Integer.parseInt(s1); - } catch (final NumberFormatException e) { - throw new ParseException("Invalid TLS major version", buffer, lowerBound, upperBound, pos); - } - final String s2 = s.substring(idx + 1); - final int minor; - try { - minor = Integer.parseInt(s2); - } catch (final NumberFormatException e) { - throw new ParseException("Invalid TLS minor version", buffer, lowerBound, upperBound, pos); - } - return new ProtocolVersion("TLS", major, minor); - } - } - - ProtocolVersion parse(final String s) throws ParseException { - if (s == null) { - return null; - } - final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); - return parse(s, cursor, null); - } - -} - diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/support/AbstractMessageBuilder.java b/httpcore5/src/main/java/org/apache/hc/core5/http/support/AbstractMessageBuilder.java index 006e6b52f2..99db30a568 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/support/AbstractMessageBuilder.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/support/AbstractMessageBuilder.java @@ -38,6 +38,7 @@ /** * Abstract {@link HttpMessage} builder. * + * @param The message type to build. * @since 5.1 */ public abstract class AbstractMessageBuilder { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/support/AbstractRequestBuilder.java b/httpcore5/src/main/java/org/apache/hc/core5/http/support/AbstractRequestBuilder.java index be0b9ab7de..4692e20f32 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/support/AbstractRequestBuilder.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/support/AbstractRequestBuilder.java @@ -49,6 +49,7 @@ /** * Builder for {@link BasicHttpRequest} instances. * + * @param The request type to build. * @since 5.1 */ public abstract class AbstractRequestBuilder extends AbstractMessageBuilder { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/support/AbstractResponseBuilder.java b/httpcore5/src/main/java/org/apache/hc/core5/http/support/AbstractResponseBuilder.java index c5d942338d..f5252a625b 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/support/AbstractResponseBuilder.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/support/AbstractResponseBuilder.java @@ -34,6 +34,7 @@ /** * Builder for {@link BasicHttpRequest} instances. * + * @param The response type to build. * @since 5.1 */ public abstract class AbstractResponseBuilder extends AbstractMessageBuilder { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/support/ExpectSupport.java b/httpcore5/src/main/java/org/apache/hc/core5/http/support/ExpectSupport.java new file mode 100644 index 0000000000..ae83814e0a --- /dev/null +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/support/ExpectSupport.java @@ -0,0 +1,71 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * 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; + } +} + diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HandlerEntry.java b/httpcore5/src/main/java/org/apache/hc/core5/http/support/Expectation.java similarity index 60% rename from httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HandlerEntry.java rename to httpcore5/src/main/java/org/apache/hc/core5/http/support/Expectation.java index eafb0f7f75..ebcce57f01 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HandlerEntry.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/support/Expectation.java @@ -24,31 +24,19 @@ * . * */ -package org.apache.hc.core5.http.impl.bootstrap; +package org.apache.hc.core5.http.support; -final class HandlerEntry { +import org.apache.hc.core5.annotation.Internal; - final String hostname; - final String uriPattern; - final T handler; - - public HandlerEntry(final String hostname, final String uriPattern, final T handler) { - this.hostname = hostname; - this.uriPattern = uriPattern; - this.handler = handler; - } +/** + * Message expectations. Presently only {@literal 100-continue} is recognized. + * + * @since 5.3 + */ +@Internal +public enum Expectation { - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append("HandlerEntry [hostname="); - builder.append(hostname); - builder.append(", uriPattern="); - builder.append(uriPattern); - builder.append(", handler="); - builder.append(handler); - builder.append("]"); - return builder.toString(); - } + CONTINUE, UNKNOWN } + diff --git a/httpcore5/src/main/java/org/apache/hc/core5/io/IOCallback.java b/httpcore5/src/main/java/org/apache/hc/core5/io/IOCallback.java index 24fb294615..e2b19d69af 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/io/IOCallback.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/io/IOCallback.java @@ -32,6 +32,7 @@ /** * Abstract I/O callback. * + * @param the type of the input to the operation. * @since 5.0 */ public interface IOCallback { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/io/SocketSupport.java b/httpcore5/src/main/java/org/apache/hc/core5/io/SocketSupport.java new file mode 100644 index 0000000000..aaf6d8cbd7 --- /dev/null +++ b/httpcore5/src/main/java/org/apache/hc/core5/io/SocketSupport.java @@ -0,0 +1,80 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by 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 + */ +@Internal +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); + } + } + +} diff --git a/httpcore5/src/main/java/org/apache/hc/core5/net/Host.java b/httpcore5/src/main/java/org/apache/hc/core5/net/Host.java index 3ef6192f17..52b8b796e9 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/net/Host.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/net/Host.java @@ -27,6 +27,7 @@ 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; @@ -50,10 +51,22 @@ public final class Host implements NamedEndpoint, Serializable { 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) == '-'); + } + public Host(final String name, final int port) { super(); - this.name = Args.notNull(name, "Host name"); - this.port = Ports.checkWithDefault(port); + 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); } @@ -63,21 +76,21 @@ static Host parse(final CharSequence s, final Tokenizer.Cursor cursor) throws UR final boolean ipv6Brackets = !cursor.atEnd() && s.charAt(cursor.getPos()) == '['; if (ipv6Brackets) { cursor.updatePos(cursor.getPos() + 1); - hostName = tokenizer.parseContent(s, cursor, URISupport.IPV6_HOST_TERMINATORS); + 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.isIPv6Address(hostName)) { + if (!InetAddressUtils.isIPv6(hostName)) { throw URISupport.createException(s, cursor, "Expected an IPv6 address"); } } else { - hostName = tokenizer.parseContent(s, cursor, URISupport.PORT_SEPARATORS); + 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.TERMINATORS); + portText = tokenizer.parseContent(s, cursor, URISupport.DELIMITERS); } final int port; if (!TextUtils.isBlank(portText)) { @@ -102,10 +115,14 @@ static Host parse(final CharSequence s) throws URISyntaxException { static void format(final StringBuilder buf, final NamedEndpoint endpoint) { final String hostName = endpoint.getHostName(); - if (InetAddressUtils.isIPv6Address(hostName)) { + if (InetAddressUtils.isIPv6(hostName)) { buf.append('[').append(hostName).append(']'); } else { - buf.append(hostName); + if (TextUtils.isAllASCII(hostName)) { + buf.append(hostName); + } else { + buf.append(IDN.toASCII(hostName)); + } } if (endpoint.getPort() != -1) { buf.append(":"); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/net/InetAddressUtils.java b/httpcore5/src/main/java/org/apache/hc/core5/net/InetAddressUtils.java index f3674574d0..f141e6b96c 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/net/InetAddressUtils.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/net/InetAddressUtils.java @@ -79,6 +79,19 @@ private InetAddressUtils() { "::" + "(([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 */ @@ -87,21 +100,45 @@ private InetAddressUtils() { // Must not have more than 7 colons (i.e. 8 fields) private static final int MAX_COLON_COUNT = 7; + /** + * @deprecated Use {@link #isIPv4(CharSequence)} + */ + @Deprecated + public static boolean isIPv4Address(final String input) { + return isIPv4(input); + } + /** * Checks whether the parameter is a valid IPv4 address * - * @param input the address string to check for validity + * @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 isIPv4Address(final String input) { + public static boolean isIPv4(final CharSequence input) { return IPV4_PATTERN.matcher(input).matches(); } + /** + * @deprecated Use {@link #isIPv4MappedIPv6(CharSequence)} + */ + @Deprecated public static boolean isIPv4MappedIPv64Address(final String input) { + return isIPv4MappedIPv6(input); + } + + /** + * Check 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 String input) { + static boolean hasValidIPv6ColonCount(final CharSequence input) { int colonCount = 0; for (int i = 0; i < input.length(); i++) { if (input.charAt(i) == COLON_CHAR) { @@ -112,45 +149,103 @@ static boolean hasValidIPv6ColonCount(final String input) { return colonCount >= 2 && colonCount <= MAX_COLON_COUNT; } + /** + * @deprecated Use {@link #isIPv6Std(CharSequence)} + */ + @Deprecated + public static boolean isIPv6StdAddress(final String input) { + return isIPv6Std(input); + } + /** * Checks whether the parameter is a valid standard (non-compressed) IPv6 address * - * @param input the address string to check for validity + * @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 isIPv6StdAddress(final String input) { + public static boolean isIPv6Std(final CharSequence input) { return hasValidIPv6ColonCount(input) && IPV6_STD_PATTERN.matcher(input).matches(); } + /** + * @deprecated Use {@link #isIPv6HexCompressed(CharSequence)} + */ + @Deprecated + public static boolean isIPv6HexCompressedAddress(final String input) { + return isIPv6HexCompressed(input); + } + /** * Checks whether the parameter is a valid compressed IPv6 address * - * @param input the address string to check for validity + * @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 isIPv6HexCompressedAddress(final String input) { + public static boolean isIPv6HexCompressed(final CharSequence input) { return hasValidIPv6ColonCount(input) && IPV6_HEX_COMPRESSED_PATTERN.matcher(input).matches(); } + /** + * @deprecated Use {@link #isIPv6(CharSequence)} + */ + @Deprecated + public static boolean isIPv6Address(final String input) { + return isIPv6(input); + } + /** * Checks whether the parameter is a valid IPv6 address (including compressed). * - * @param input the address string to check for validity + * @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 isIPv6Address(final String input) { - return isIPv6StdAddress(input) || isIPv6HexCompressedAddress(input); + 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; + } + + /** + * @deprecated Use {@link #isIPv6URLBracketed(CharSequence)} + */ + @Deprecated + public static boolean isIPv6URLBracketedAddress(final String input) { + return isIPv6URLBracketed(input); } /** * Checks 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 string to check for validity + * @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 isIPv6URLBracketedAddress(final String input) { - return input.startsWith("[") && input.endsWith("]") && isIPv6Address(input.substring(1, input.length() - 1)); + 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)); } /** diff --git a/httpcore5/src/main/java/org/apache/hc/core5/net/PercentCodec.java b/httpcore5/src/main/java/org/apache/hc/core5/net/PercentCodec.java index 2782282a4f..40ebc9cb81 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/net/PercentCodec.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/net/PercentCodec.java @@ -34,7 +34,7 @@ import java.util.BitSet; /** - * Percent-encoding mechanism defined in RFC 3986 + * Percent-encoding. * * @since 5.1 */ @@ -84,6 +84,35 @@ public class PercentCodec { 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('~'); + } + private static final int RADIX = 16; static void encode(final StringBuilder buf, final CharSequence content, final Charset charset, @@ -160,4 +189,43 @@ 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); + } + } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/net/URIAuthority.java b/httpcore5/src/main/java/org/apache/hc/core5/net/URIAuthority.java index a9fed758c1..c1659bb440 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/net/URIAuthority.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/net/URIAuthority.java @@ -28,6 +28,7 @@ package org.apache.hc.core5.net; import java.io.Serializable; +import java.net.URI; import java.net.URISyntaxException; import java.util.Objects; @@ -39,7 +40,7 @@ import org.apache.hc.core5.util.Tokenizer; /** - * Represents authority component of request {@link java.net.URI}. + * Represents authority component of request {@link URI}. * * @since 5.0 */ @@ -54,7 +55,7 @@ static URIAuthority parse(final CharSequence s, final Tokenizer.Cursor cursor) t final Tokenizer tokenizer = Tokenizer.INSTANCE; String userInfo = null; final int initPos = cursor.getPos(); - final String token = tokenizer.parseContent(s, cursor, URISupport.HOST_SEPARATORS); + 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)) { @@ -137,6 +138,10 @@ public URIAuthority(final NamedEndpoint 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)) { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/net/URIBuilder.java b/httpcore5/src/main/java/org/apache/hc/core5/net/URIBuilder.java index 8212db6161..048f60fd29 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/net/URIBuilder.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/net/URIBuilder.java @@ -34,13 +34,13 @@ 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; @@ -138,7 +138,7 @@ public URIBuilder(final URI uri, final Charset charset) { * Sets the authority. * * @param authority the authority. - * @return this. + * @return this instance. * @since 5.2 */ public URIBuilder setAuthority(final NamedEndpoint authority) { @@ -152,7 +152,7 @@ public URIBuilder setAuthority(final NamedEndpoint authority) { * Sets the authority. * * @param authority the authority. - * @return this. + * @return this instance. * @since 5.2 */ public URIBuilder setAuthority(final URIAuthority authority) { @@ -166,7 +166,7 @@ public URIBuilder setAuthority(final URIAuthority authority) { * Sets the Charset. * * @param charset the Charset. - * @return this. + * @return this instance. */ public URIBuilder setCharset(final Charset charset) { this.charset = charset; @@ -196,16 +196,8 @@ public Charset getCharset() { private static final char PARAM_VALUE_SEPARATOR = '='; private static final char PATH_SEPARATOR = '/'; - private static final BitSet QUERY_PARAM_SEPARATORS = new BitSet(256); - private static final BitSet QUERY_VALUE_SEPARATORS = new BitSet(256); - private static final BitSet PATH_SEPARATORS = new BitSet(256); - - static { - QUERY_PARAM_SEPARATORS.set(QUERY_PARAM_SEPARATOR); - QUERY_PARAM_SEPARATORS.set(PARAM_VALUE_SEPARATOR); - QUERY_VALUE_SEPARATORS.set(QUERY_PARAM_SEPARATOR); - PATH_SEPARATORS.set(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) { @@ -238,14 +230,14 @@ static List parseQuery(final CharSequence s, final Charset charse static List splitPath(final CharSequence s) { if (s == null) { - return null; + return Collections.emptyList(); } final ParserCursor cursor = new ParserCursor(0, s.length()); // Skip leading separator if (cursor.atEnd()) { return new ArrayList<>(0); } - if (PATH_SEPARATORS.get(s.charAt(cursor.getPos()))) { + if (s.charAt(cursor.getPos()) == PATH_SEPARATOR) { cursor.updatePos(cursor.getPos() + 1); } final List list = new ArrayList<>(); @@ -256,7 +248,7 @@ static List splitPath(final CharSequence s) { break; } final char current = s.charAt(cursor.getPos()); - if (PATH_SEPARATORS.get(current)) { + if (current == PATH_SEPARATOR) { list.add(buf.toString()); buf.setLength(0); } else { @@ -269,7 +261,7 @@ static List splitPath(final CharSequence s) { static List parsePath(final CharSequence s, final Charset charset) { if (s == null) { - return null; + return Collections.emptyList(); } final List segments = splitPath(s); final List list = new ArrayList<>(segments.size()); @@ -310,6 +302,9 @@ static void formatQuery(final StringBuilder buf, final Iterable nvps) { @@ -472,7 +467,7 @@ public URIBuilder setSchemeSpecificPart(final String schemeSpecificPart, final L * Sets URI user info. The value is expected to be unescaped and may contain non ASCII * characters. * - * @return this. + * @return this instance. */ public URIBuilder setUserInfo(final String userInfo) { this.userInfo = !TextUtils.isBlank(userInfo) ? userInfo : null; @@ -486,7 +481,7 @@ public URIBuilder setUserInfo(final String userInfo) { * 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. + * @return this instance. * * @deprecated The use of clear-text passwords in {@link URI}s has been deprecated and is strongly * discouraged. @@ -499,7 +494,7 @@ public URIBuilder setUserInfo(final String username, final String password) { /** * Sets URI host. * - * @return this. + * @return this instance. */ public URIBuilder setHost(final InetAddress host) { this.host = host != null ? host.getHostAddress() : null; @@ -513,7 +508,7 @@ public URIBuilder setHost(final InetAddress host) { * {@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. + * @return this instance. */ public URIBuilder setHost(final String host) { this.host = host; @@ -526,7 +521,7 @@ public URIBuilder setHost(final String host) { * Sets the scheme, host name, and port. * * @param httpHost the scheme, host name, and port. - * @return this. + * @return this instance. */ public URIBuilder setHttpHost(final HttpHost httpHost) { setScheme(httpHost.getSchemeName()); @@ -538,7 +533,7 @@ public URIBuilder setHttpHost(final HttpHost httpHost) { /** * Sets URI port. * - * @return this. + * @return this instance. */ public URIBuilder setPort(final int port) { this.port = port < 0 ? -1 : port; @@ -550,7 +545,7 @@ public URIBuilder setPort(final int port) { /** * Sets URI path. The value is expected to be unescaped and may contain non ASCII characters. * - * @return this. + * @return this instance. */ public URIBuilder setPath(final String path) { setPathSegments(path != null ? splitPath(path) : null); @@ -561,7 +556,7 @@ public URIBuilder setPath(final String path) { /** * Appends path to URI. The value is expected to be unescaped and may contain non ASCII characters. * - * @return this. + * @return this instance. */ public URIBuilder appendPath(final String path) { if (path != null) { @@ -573,7 +568,7 @@ public URIBuilder appendPath(final String path) { /** * Sets URI path. The value is expected to be unescaped and may contain non ASCII characters. * - * @return this. + * @return this instance. */ public URIBuilder setPathSegments(final String... pathSegments) { return setPathSegments(Arrays.asList(pathSegments)); @@ -582,7 +577,7 @@ public URIBuilder setPathSegments(final String... pathSegments) { /** * Appends segments URI path. The value is expected to be unescaped and may contain non ASCII characters. * - * @return this. + * @return this instance. */ public URIBuilder appendPathSegments(final String... pathSegments) { return appendPathSegments(Arrays.asList(pathSegments)); @@ -592,7 +587,7 @@ public URIBuilder appendPathSegments(final String... 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. + * @return this instance. * * @since 5.1 */ @@ -603,7 +598,7 @@ public URIBuilder setPathSegmentsRootless(final String... pathSegments) { /** * Sets URI path. The value is expected to be unescaped and may contain non ASCII characters. * - * @return this. + * @return this instance. */ public URIBuilder setPathSegments(final List pathSegments) { this.pathSegments = pathSegments != null && !pathSegments.isEmpty() ? new ArrayList<>(pathSegments) : null; @@ -616,7 +611,7 @@ public URIBuilder setPathSegments(final List pathSegments) { /** * Appends segments to URI path. The value is expected to be unescaped and may contain non ASCII characters. * - * @return this. + * @return this instance. */ public URIBuilder appendPathSegments(final List pathSegments) { if (pathSegments != null && !pathSegments.isEmpty()) { @@ -634,7 +629,7 @@ public URIBuilder appendPathSegments(final List 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. + * @return this instance. * * @since 5.1 */ @@ -649,7 +644,7 @@ public URIBuilder setPathSegmentsRootless(final List pathSegments) { /** * Removes URI query. * - * @return this. + * @return this instance. */ public URIBuilder removeQuery() { this.queryParams = null; @@ -667,7 +662,7 @@ public URIBuilder removeQuery() { * will remove custom query if present. *

* - * @return this. + * @return this instance. */ public URIBuilder setParameters(final List nameValuePairs) { if (this.queryParams == null) { @@ -692,7 +687,7 @@ public URIBuilder setParameters(final List nameValuePairs) { * will remove custom query if present. *

* - * @return this. + * @return this instance. */ public URIBuilder addParameters(final List nameValuePairs) { if (this.queryParams == null) { @@ -715,7 +710,7 @@ public URIBuilder addParameters(final List nameValuePairs) { * will remove custom query if present. *

* - * @return this. + * @return this instance. */ public URIBuilder setParameters(final NameValuePair... nameValuePairs) { if (this.queryParams == null) { @@ -740,7 +735,7 @@ public URIBuilder setParameters(final NameValuePair... nameValuePairs) { * will remove custom query if present. *

* - * @return this. + * @return this instance. */ public URIBuilder addParameter(final String param, final String value) { return addParameter(new BasicNameValuePair(param, value)); @@ -754,7 +749,7 @@ public URIBuilder addParameter(final String param, final String value) { * will remove custom query if present. *

* - * @return this. + * @return this instance. * @since 5.2 */ public URIBuilder addParameter(final NameValuePair nameValuePair) { @@ -778,7 +773,7 @@ public URIBuilder addParameter(final NameValuePair nameValuePair) { * will remove custom query if present, even when no parameter was actually removed. *

* - * @return this. + * @return this instance. * @since 5.2 */ public URIBuilder removeParameter(final String param) { @@ -800,7 +795,7 @@ public URIBuilder removeParameter(final String param) { * will remove custom query if present. *

* - * @return this. + * @return this instance. */ public URIBuilder setParameter(final String param, final String value) { if (this.queryParams == null) { @@ -819,7 +814,7 @@ public URIBuilder setParameter(final String param, final String value) { /** * Clears URI query parameters. * - * @return this. + * @return this instance. */ public URIBuilder clearParameters() { this.queryParams = null; @@ -836,7 +831,7 @@ public URIBuilder clearParameters() { * will remove query parameters if present. *

* - * @return this. + * @return this instance. */ public URIBuilder setCustomQuery(final String query) { this.query = !TextUtils.isBlank(query) ? query : null; @@ -850,7 +845,7 @@ public URIBuilder setCustomQuery(final String query) { * Sets URI fragment. The value is expected to be unescaped and may contain non ASCII * characters. * - * @return this. + * @return this instance. */ public URIBuilder setFragment(final String fragment) { this.fragment = !TextUtils.isBlank(fragment) ? fragment : null; @@ -984,6 +979,9 @@ public List getQueryParams() { * @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); } @@ -997,17 +995,29 @@ public String getFragment() { } /** - * Normalizes syntax of URI components if the URI is considered non-opaque - * (the path component has a root): + * @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 normalized
  • + *
  • 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.1 + * @since 5.3 */ - public URIBuilder normalizeSyntax() { + public URIBuilder optimize() { final String scheme = this.scheme; if (scheme != null) { this.scheme = TextUtils.toLowerCase(scheme); @@ -1017,7 +1027,7 @@ public URIBuilder normalizeSyntax() { return this; } - // Force Percent-Encoding normalization + // Force Percent-Encoding re-encoding this.encodedSchemeSpecificPart = null; this.encodedAuthority = null; this.encodedUserInfo = null; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/net/URISupport.java b/httpcore5/src/main/java/org/apache/hc/core5/net/URISupport.java index e8847940ff..350ca11f90 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/net/URISupport.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/net/URISupport.java @@ -33,10 +33,9 @@ final class URISupport { - static final BitSet HOST_SEPARATORS = new BitSet(256); - static final BitSet IPV6_HOST_TERMINATORS = new BitSet(256); - static final BitSet PORT_SEPARATORS = new BitSet(256); - static final BitSet TERMINATORS = new BitSet(256); + 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('/'); @@ -44,11 +43,15 @@ final class URISupport { TERMINATORS.set('?'); HOST_SEPARATORS.or(TERMINATORS); HOST_SEPARATORS.set('@'); - IPV6_HOST_TERMINATORS.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( diff --git a/httpcore5/src/main/java/org/apache/hc/core5/net/WWWFormCodec.java b/httpcore5/src/main/java/org/apache/hc/core5/net/WWWFormCodec.java index 900918e342..5161040e51 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/net/WWWFormCodec.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/net/WWWFormCodec.java @@ -39,8 +39,6 @@ */ public class WWWFormCodec { - private static final char QP_SEP_A = '&'; - /** * Returns a list of {@link NameValuePair} parameters parsed * from the {@code application/x-www-form-urlencoded} content. diff --git a/httpcore5/src/main/java/org/apache/hc/core5/pool/ConnPoolListener.java b/httpcore5/src/main/java/org/apache/hc/core5/pool/ConnPoolListener.java index 11dc9ec8e8..4dce6e2cf3 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/pool/ConnPoolListener.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/pool/ConnPoolListener.java @@ -32,6 +32,7 @@ /** * Connection pool event listener. * + * @param the route type that represents the opposite endpoint of a pooled connection. * @since 5.0 */ @Contract(threading = ThreadingBehavior.STATELESS) diff --git a/httpcore5/src/main/java/org/apache/hc/core5/pool/LaxConnPool.java b/httpcore5/src/main/java/org/apache/hc/core5/pool/LaxConnPool.java index 4a40470eec..b680ae63df 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/pool/LaxConnPool.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/pool/LaxConnPool.java @@ -96,7 +96,7 @@ public LaxConnPool( this.disposalCallback = disposalCallback; this.connPoolListener = connPoolListener; this.routeToPool = new ConcurrentHashMap<>(); - this.isShutDown = new AtomicBoolean(false); + this.isShutDown = new AtomicBoolean(); this.defaultMaxPerRoute = defaultMaxPerRoute; } @@ -382,7 +382,7 @@ private enum RequestServiceStrategy { FIRST_SUCCESSFUL, ALL } this.leased = new ConcurrentHashMap<>(); this.available = new ConcurrentLinkedDeque<>(); this.pending = new ConcurrentLinkedDeque<>(); - this.terminated = new AtomicBoolean(false); + this.terminated = new AtomicBoolean(); this.allocated = new AtomicInteger(0); this.releaseSeqNum = new AtomicLong(0); this.max = max; @@ -462,7 +462,7 @@ public Future> lease( final BasicFuture> future = new BasicFuture>(callback) { @Override - public synchronized PoolEntry get( + public PoolEntry get( final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { try { return super.get(timeout, unit); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/pool/StrictConnPool.java b/httpcore5/src/main/java/org/apache/hc/core5/pool/StrictConnPool.java index 0c7ca8faaf..819238b38b 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/pool/StrictConnPool.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/pool/StrictConnPool.java @@ -40,7 +40,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.core5.annotation.Contract; @@ -78,7 +77,7 @@ public class StrictConnPool implements ManagedConnP private final LinkedList> available; private final ConcurrentLinkedQueue> completedRequests; private final Map maxPerRoute; - private final Lock lock; + private final ReentrantLock lock; private final AtomicBoolean isShutDown; private volatile int defaultMaxPerRoute; @@ -108,7 +107,7 @@ public StrictConnPool( this.completedRequests = new ConcurrentLinkedQueue<>(); this.maxPerRoute = new HashMap<>(); this.lock = new ReentrantLock(); - this.isShutDown = new AtomicBoolean(false); + this.isShutDown = new AtomicBoolean(); this.defaultMaxPerRoute = defaultMaxPerRoute; this.maxTotal = maxTotal; } @@ -178,7 +177,7 @@ public Future> lease( final BasicFuture> future = new BasicFuture>(callback) { @Override - public synchronized PoolEntry get( + public PoolEntry get( final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { try { return super.get(timeout, unit); @@ -192,7 +191,7 @@ public synchronized PoolEntry get( final boolean acquiredLock; try { - if (Timeout.isPositive(requestTimeout)) { + if (TimeValue.isPositive(requestTimeout)) { acquiredLock = this.lock.tryLock(requestTimeout.getDuration(), requestTimeout.getTimeUnit()); } else { this.lock.lockInterruptibly(); @@ -696,7 +695,7 @@ public LeaseRequest( this.state = state; this.deadline = Deadline.calculate(requestTimeout); this.future = future; - this.completed = new AtomicBoolean(false); + this.completed = new AtomicBoolean(); } public T getRoute() { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/AbstractIOSessionPool.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/AbstractIOSessionPool.java index 339d4ee557..3691264aab 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/AbstractIOSessionPool.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/AbstractIOSessionPool.java @@ -34,6 +34,7 @@ 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; @@ -58,10 +59,13 @@ 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(false); + this.closed = new AtomicBoolean(); + this.lock = new ReentrantLock(); } protected abstract Future connectSession( @@ -81,7 +85,8 @@ protected abstract void closeSession( public final void close(final CloseMode closeMode) { if (closed.compareAndSet(false, true)) { for (final PoolEntry poolEntry : sessionPool.values()) { - synchronized (poolEntry) { + lock.lock(); + try { if (poolEntry.session != null) { closeSession(poolEntry.session, closeMode); poolEntry.session = null; @@ -98,6 +103,8 @@ public final void close(final CloseMode closeMode) { break; } } + } finally { + lock.unlock(); } } sessionPool.clear(); @@ -170,7 +177,8 @@ private void getSessionInternal( final T namedEndpoint, final Timeout connectTimeout, final FutureCallback callback) { - synchronized (poolEntry) { + poolEntry.lock.lock(); + try { if (poolEntry.session != null && requestNew) { closeSession(poolEntry.session, CloseMode.GRACEFUL); poolEntry.session = null; @@ -194,7 +202,8 @@ private void getSessionInternal( @Override public void completed(final IOSession result) { - synchronized (poolEntry) { + poolEntry.lock.lock(); + try { poolEntry.completed = true; if (poolEntry.session == null) { poolEntry.session = result; @@ -209,12 +218,15 @@ public void completed(final IOSession result) { break; } } + } finally { + poolEntry.lock.unlock(); } } @Override public void failed(final Exception ex) { - synchronized (poolEntry) { + poolEntry.lock.lock(); + try { poolEntry.completed = true; poolEntry.session = null; for (;;) { @@ -225,6 +237,8 @@ public void failed(final Exception ex) { break; } } + } finally { + poolEntry.lock.unlock(); } } @@ -236,19 +250,24 @@ public void cancelled() { }); } } + } finally { + poolEntry.lock.unlock(); } } public final void enumAvailable(final Callback callback) { for (final PoolEntry poolEntry: sessionPool.values()) { if (poolEntry.session != null) { - synchronized (poolEntry) { + lock.lock(); + try { if (poolEntry.session != null) { callback.execute(poolEntry.session); if (!poolEntry.session.isOpen()) { poolEntry.session = null; } } + } finally { + lock.unlock(); } } } @@ -258,11 +277,14 @@ 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) { - synchronized (poolEntry) { + lock.lock(); + try { if (poolEntry.session != null && poolEntry.session.getLastReadTime() <= deadline) { closeSession(poolEntry.session, CloseMode.GRACEFUL); poolEntry.session = null; } + } finally { + lock.unlock(); } } } @@ -286,9 +308,11 @@ static class PoolEntry { volatile boolean completed; volatile Future sessionFuture; volatile IOSession session; + final ReentrantLock lock; // Added PoolEntry() { this.requestQueue = new ArrayDeque<>(); + this.lock = new ReentrantLock(); } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/AbstractSingleCoreIOReactor.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/AbstractSingleCoreIOReactor.java index b2834d3ed6..fc3de36f1f 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/AbstractSingleCoreIOReactor.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/AbstractSingleCoreIOReactor.java @@ -33,8 +33,11 @@ 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; @@ -48,14 +51,15 @@ abstract class AbstractSingleCoreIOReactor implements IOReactor { private final Callback exceptionCallback; private final AtomicReference status; private final AtomicBoolean terminated; - private final Object shutdownMutex; + private final Condition condition; + + private final ReentrantLock lock; final Selector selector; AbstractSingleCoreIOReactor(final Callback exceptionCallback) { super(); this.exceptionCallback = exceptionCallback; - this.shutdownMutex = new Object(); this.status = new AtomicReference<>(IOReactorStatus.INACTIVE); this.terminated = new AtomicBoolean(); try { @@ -63,6 +67,8 @@ abstract class AbstractSingleCoreIOReactor implements IOReactor { } catch (final IOException ex) { throw new IllegalStateException("Unexpected failure opening I/O selector", ex); } + this.lock = new ReentrantLock(); + this.condition = lock.newCondition(); } @Override @@ -105,22 +111,28 @@ public final void awaitShutdown(final TimeValue waitTime) throws InterruptedExce Args.notNull(waitTime, "Wait time"); final long deadline = System.currentTimeMillis() + waitTime.toMilliseconds(); long remaining = waitTime.toMilliseconds(); - synchronized (this.shutdownMutex) { + lock.lock(); + try { while (this.status.get().compareTo(IOReactorStatus.SHUT_DOWN) < 0) { - this.shutdownMutex.wait(remaining); + 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)) { - synchronized (this.shutdownMutex) { - this.shutdownMutex.notifyAll(); + lock.lock(); + try { + condition.signalAll(); + } finally { + lock.unlock(); } } else if (this.status.compareAndSet(IOReactorStatus.ACTIVE, IOReactorStatus.SHUTTING_DOWN)) { this.selector.wakeup(); @@ -169,8 +181,11 @@ public void close(final CloseMode closeMode, final Timeout timeout) { logException(ex); } } - synchronized (this.shutdownMutex) { - this.shutdownMutex.notifyAll(); + lock.lock(); + try { + condition.signalAll(); + } finally { + lock.unlock(); } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOEventHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOEventHandler.java index b747ef246f..8f591650a3 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOEventHandler.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOEventHandler.java @@ -49,6 +49,7 @@ 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; @@ -56,6 +57,7 @@ public interface IOEventHandler { * 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; @@ -63,6 +65,7 @@ public interface IOEventHandler { * 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; @@ -71,6 +74,7 @@ public interface IOEventHandler { * * @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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactor.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactor.java index 0cea506e23..1c76534611 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactor.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactor.java @@ -81,7 +81,8 @@ public interface IOReactor extends ModalCloseable { * 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; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactorConfig.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactorConfig.java index 034fce9eaa..d480cf72a5 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactorConfig.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactorConfig.java @@ -28,6 +28,7 @@ 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; @@ -57,6 +58,9 @@ public final class IOReactorConfig { 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; @@ -73,6 +77,9 @@ public final class IOReactorConfig { 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) { @@ -88,6 +95,9 @@ public final class IOReactorConfig { 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; @@ -182,6 +192,33 @@ 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) */ @@ -220,6 +257,9 @@ public static Builder copy(final IOReactorConfig config) { .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()); @@ -265,6 +305,9 @@ public static void setDefaultMaxIOThreadCount(final int defaultMaxIOThreadCount) 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; @@ -281,17 +324,22 @@ public static void setDefaultMaxIOThreadCount(final int defaultMaxIOThreadCount) this.sndBufSize = 0; this.rcvBufSize = 0; this.backlogSize = 0; + this.tcpKeepIdle = -1; + this.tcpKeepInterval = -1; + this.tcpKeepCount = -1; this.socksProxyAddress = null; this.socksProxyUsername = null; this.socksProxyPassword = null; } /** - * Determines time interval at which the I/O reactor wakes up to check for timed out sessions + * 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; @@ -299,10 +347,12 @@ public Builder setSelectInterval(final TimeValue selectInterval) { } /** - * Determines the number of I/O dispatch threads to be used by the I/O reactor. + * 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; @@ -310,12 +360,13 @@ public Builder setIoThreadCount(final int ioThreadCount) { } /** - * Determines the default socket timeout value for non-blocking I/O operations. + * Sets the default socket timeout value for non-blocking I/O operations. *

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

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

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

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

* Default: {@code false} *

* - * @see java.net.SocketOptions#SO_REUSEADDR + * @return this instance. + * @see SocketOptions#SO_REUSEADDR */ public Builder setSoReuseAddress(final boolean soReuseAddress) { this.soReuseAddress = soReuseAddress; @@ -350,13 +403,14 @@ public Builder setSoReuseAddress(final boolean soReuseAddress) { } /** - * Determines the default value of the {@link java.net.SocketOptions#SO_LINGER} parameter + * Sets the default value of the {@link SocketOptions#SO_LINGER} parameter * for newly created sockets. *

* Default: {@code -1} *

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

* Default: {@code -1} *

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

* Default: {@code -1} *

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

* Default: {@code false} *

* - * @see java.net.SocketOptions#TCP_NODELAY + * @return this instance. + * @see SocketOptions#TCP_NODELAY */ public Builder setTcpNoDelay(final boolean tcpNoDelay) { this.tcpNoDelay = tcpNoDelay; @@ -406,13 +463,14 @@ public Builder setTcpNoDelay(final boolean tcpNoDelay) { } /** - * Determines the default value of the {@link java.net.SocketOptions#IP_TOS} parameter + * Sets the default value of the {@link SocketOptions#IP_TOS} parameter * for newly created sockets. *

* Default: {@code 0} *

* - * @see java.net.SocketOptions#IP_TOS + * @return this instance. + * @see SocketOptions#IP_TOS * * @since 5.1 */ @@ -422,13 +480,14 @@ public Builder setTrafficClass(final int trafficClass) { } /** - * Determines the default value of the {@link java.net.SocketOptions#SO_SNDBUF} parameter + * Sets the default value of the {@link SocketOptions#SO_SNDBUF} parameter * for newly created sockets. *

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

* - * @see java.net.SocketOptions#SO_SNDBUF + * @return this instance. + * @see SocketOptions#SO_SNDBUF */ public Builder setSndBufSize(final int sndBufSize) { this.sndBufSize = sndBufSize; @@ -436,13 +495,14 @@ public Builder setSndBufSize(final int sndBufSize) { } /** - * Determines the default value of the {@link java.net.SocketOptions#SO_RCVBUF} parameter + * Sets the default value of the {@link SocketOptions#SO_RCVBUF} parameter * for newly created sockets. *

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

* - * @see java.net.SocketOptions#SO_RCVBUF + * @return this instance. + * @see SocketOptions#SO_RCVBUF */ public Builder setRcvBufSize(final int rcvBufSize) { this.rcvBufSize = rcvBufSize; @@ -450,11 +510,12 @@ public Builder setRcvBufSize(final int rcvBufSize) { } /** - * Determines the default backlog size value for server sockets binds. + * 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) { @@ -463,7 +524,43 @@ public Builder setBacklogSize(final int backlogSize) { } /** - * The address of the SOCKS proxy to use. + * Sets the time (in seconds) the connection needs to remain idle before TCP starts + * sending keepalive probes. + * + * @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. + * + * @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. + * + * @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; @@ -471,7 +568,9 @@ public Builder setSocksProxyAddress(final SocketAddress socksProxyAddress) { } /** - * The username to provide to the SOCKS proxy for username/password authentication. + * 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; @@ -479,7 +578,9 @@ public Builder setSocksProxyUsername(final String socksProxyUsername) { } /** - * The password to provide to the SOCKS proxy for username/password authentication. + * 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; @@ -490,13 +591,14 @@ public IOReactorConfig build() { return new IOReactorConfig( selectInterval != null ? selectInterval : TimeValue.ofSeconds(1), ioThreadCount, - Timeout.defaultsToDisabled(soTimeout), + Timeout.defaultsToInfinite(soTimeout), soReuseAddress, TimeValue.defaultsToNegativeOneMillisecond(soLinger), soKeepAlive, tcpNoDelay, trafficClass, sndBufSize, rcvBufSize, backlogSize, + tcpKeepIdle, tcpKeepInterval, tcpKeepCount, socksProxyAddress, socksProxyUsername, socksProxyPassword); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOSessionImpl.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOSessionImpl.java index 19235f575c..45379c0560 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOSessionImpl.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOSessionImpl.java @@ -70,7 +70,7 @@ public IOSessionImpl(final String type, final SelectionKey key, final SocketChan this.channel = Args.notNull(socketChannel, "Socket channel"); this.commandQueue = new ConcurrentLinkedDeque<>(); this.lock = new ReentrantLock(); - this.socketTimeout = Timeout.DISABLED; + this.socketTimeout = Timeout.INFINITE; this.id = String.format(type + "-%010d", COUNT.getAndIncrement()); this.handlerRef = new AtomicReference<>(); this.status = new AtomicReference<>(Status.ACTIVE); @@ -193,7 +193,7 @@ public Timeout getSocketTimeout() { @Override public void setSocketTimeout(final Timeout timeout) { - this.socketTimeout = Timeout.defaultsToDisabled(timeout); + this.socketTimeout = Timeout.defaultsToInfinite(timeout); this.lastEventTime = System.currentTimeMillis(); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/InternalDataChannel.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/InternalDataChannel.java index f28da03ade..5d64c00fb4 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/InternalDataChannel.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/InternalDataChannel.java @@ -32,6 +32,7 @@ import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.nio.channels.SelectionKey; +import java.util.Objects; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -88,7 +89,7 @@ final class InternalDataChannel extends InternalChannel implements ProtocolIOSes ioSessionDecorator != null ? ioSessionDecorator.decorate(ioSession) : ioSession); this.eventHandlerRef = new AtomicReference<>(); this.protocolUpgradeHandlerMap = new ConcurrentHashMap<>(); - this.closed = new AtomicBoolean(false); + this.closed = new AtomicBoolean(); } @Override @@ -433,11 +434,7 @@ public void registerProtocol(final String protocolId, final ProtocolUpgradeHandl @Override public String toString() { final IOSession currentSession = currentSessionRef.get(); - if (currentSession != null) { - return currentSession.toString(); - } else { - return ioSession.toString(); - } + return Objects.toString(currentSession != null ? currentSession: ioSession, null); } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/ListenerEndpointImpl.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/ListenerEndpointImpl.java index f5ac69827d..224095681b 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/ListenerEndpointImpl.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/ListenerEndpointImpl.java @@ -47,7 +47,7 @@ public ListenerEndpointImpl(final SelectionKey key, final Object attachment, fin this.key = key; this.address = address; this.attachment = attachment; - this.closed = new AtomicBoolean(false); + this.closed = new AtomicBoolean(); } @Override diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/MultiCoreIOReactor.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/MultiCoreIOReactor.java index 7be876463f..957a772eb4 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/MultiCoreIOReactor.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/MultiCoreIOReactor.java @@ -59,10 +59,12 @@ public IOReactorStatus getStatus() { /** * 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)) { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreIOReactor.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreIOReactor.java index a9c9365547..8661d67f29 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreIOReactor.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreIOReactor.java @@ -31,14 +31,12 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; +import java.net.SocketOption; 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.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; @@ -50,9 +48,9 @@ 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.io.SocketSupport; import org.apache.hc.core5.net.NamedEndpoint; import org.apache.hc.core5.util.Args; -import org.apache.hc.core5.util.Asserts; import org.apache.hc.core5.util.Timeout; class SingleCoreIOReactor extends AbstractSingleCoreIOReactor implements ConnectionInitiator { @@ -84,7 +82,7 @@ class SingleCoreIOReactor extends AbstractSingleCoreIOReactor implements Connect this.ioSessionDecorator = ioSessionDecorator; this.sessionListener = sessionListener; this.sessionShutdownCallback = sessionShutdownCallback; - this.shutdownInitiated = new AtomicBoolean(false); + this.shutdownInitiated = new AtomicBoolean(); this.closedSessions = new ConcurrentLinkedQueue<>(); this.channelQueue = new ConcurrentLinkedQueue<>(); this.requestQueue = new ConcurrentLinkedQueue<>(); @@ -190,7 +188,7 @@ private void processPendingChannels() throws IOException { final SocketChannel socketChannel = entry.channel; final Object attachment = entry.attachment; try { - prepareSocket(socketChannel.socket()); + prepareSocket(socketChannel); socketChannel.configureBlocking(false); } catch (final IOException ex) { logException(ex); @@ -265,7 +263,8 @@ public Future connect( return sessionRequest; } - private void prepareSocket(final Socket socket) throws IOException { + private void prepareSocket(final SocketChannel socketChannel) throws IOException { + final Socket socket = socketChannel.socket(); socket.setTcpNoDelay(this.reactorConfig.isTcpNoDelay()); socket.setKeepAlive(this.reactorConfig.isSoKeepAlive()); if (this.reactorConfig.getSndBufSize() > 0) { @@ -281,6 +280,27 @@ private void prepareSocket(final Socket socket) throws IOException { if (linger >= 0) { socket.setSoLinger(true, linger); } + if (this.reactorConfig.getTcpKeepIdle() > 0) { + setExtendedSocketOption(socketChannel, SocketSupport.TCP_KEEPIDLE, this.reactorConfig.getTcpKeepIdle()); + } + if (this.reactorConfig.getTcpKeepInterval() > 0) { + setExtendedSocketOption(socketChannel, SocketSupport.TCP_KEEPINTERVAL, this.reactorConfig.getTcpKeepInterval()); + } + if (this.reactorConfig.getTcpKeepInterval() > 0) { + setExtendedSocketOption(socketChannel, SocketSupport.TCP_KEEPCOUNT, this.reactorConfig.getTcpKeepCount()); + } + } + + /** + * @since 5.3 + */ + void setExtendedSocketOption(final SocketChannel socketChannel, + final String optionName, final T value) throws IOException { + final SocketOption socketOption = SocketSupport.getExtendedSocketOptionOrNull(optionName); + if (socketOption == null) { + throw new UnsupportedOperationException(optionName + " is not supported in the current jdk"); + } + socketChannel.setOption(socketOption, value); } private void validateAddress(final SocketAddress address) throws UnknownHostException { @@ -315,7 +335,7 @@ private void processPendingConnectionRequests() { private void processConnectionRequest(final SocketChannel socketChannel, final IOSessionRequest sessionRequest) throws IOException { socketChannel.configureBlocking(false); - prepareSocket(socketChannel.socket()); + prepareSocket(socketChannel); validateAddress(sessionRequest.localAddress); if (sessionRequest.localAddress != null) { @@ -330,17 +350,7 @@ private void processConnectionRequest(final SocketChannel socketChannel, final I // 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; - try { - connected = AccessController.doPrivileged( - (PrivilegedExceptionAction) () -> socketChannel.connect(remoteAddress)); - } catch (final PrivilegedActionException e) { - Asserts.check(e.getCause() instanceof IOException, - "method contract violation only checked exceptions are wrapped: " + e.getCause()); - // only checked exceptions are wrapped - error and RTExceptions are rethrown by doPrivileged - throw (IOException) e.getCause(); - } - + final boolean connected = socketChannel.connect(remoteAddress); final SelectionKey key = socketChannel.register(this.selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ); final IOSession ioSession = new IOSessionImpl("c", key, socketChannel); final InternalDataChannel dataChannel = new InternalDataChannel( diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreListeningIOReactor.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreListeningIOReactor.java index a06b66d927..c00f87081a 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreListeningIOReactor.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreListeningIOReactor.java @@ -68,7 +68,7 @@ class SingleCoreListeningIOReactor extends AbstractSingleCoreIOReactor implement this.callback = callback; this.requestQueue = new ConcurrentLinkedQueue<>(); this.endpoints = new ConcurrentHashMap<>(); - this.paused = new AtomicBoolean(false); + this.paused = new AtomicBoolean(); this.selectTimeoutMillis = this.reactorConfig.getSelectInterval().toMilliseconds(); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/SocksProxyProtocolHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/SocksProxyProtocolHandler.java index 5db00a28cd..e3565be0e3 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/SocksProxyProtocolHandler.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/SocksProxyProtocolHandler.java @@ -142,6 +142,8 @@ 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"); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/ssl/SSLIOSession.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/ssl/SSLIOSession.java index ae0c1882b3..b48c495431 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/ssl/SSLIOSession.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/ssl/SSLIOSession.java @@ -232,8 +232,12 @@ public void exception(final IOSession protocolSession, final Exception cause) { } final IOEventHandler handler = session.getHandler(); if (handshakeStateRef.get() != TLSHandShakeState.COMPLETE) { - session.close(CloseMode.GRACEFUL); - close(CloseMode.IMMEDIATE); + if (cause instanceof SSLHandshakeException) { + close(CloseMode.GRACEFUL); + } else { + session.close(CloseMode.GRACEFUL); + close(CloseMode.IMMEDIATE); + } } if (handler != null) { handler.exception(protocolSession, cause); @@ -467,13 +471,17 @@ private void updateEventMask() { this.sslEngine.closeOutbound(); this.outboundClosedCount.incrementAndGet(); } - if (this.status == Status.CLOSING && this.sslEngine.isOutboundDone() + 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())) { this.status = Status.CLOSED; } // Abnormal session termination if (this.status.compareTo(Status.CLOSING) <= 0 && this.endOfStream - && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) { + && handshakeStatus == HandshakeStatus.NEED_UNWRAP) { this.status = Status.CLOSED; } if (this.status == Status.CLOSED) { @@ -484,7 +492,7 @@ private void updateEventMask() { return; } // Is there a task pending? - if (this.sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { + if (handshakeStatus == HandshakeStatus.NEED_TASK) { doRunTask(); } // Need to toggle the event mask for this channel? @@ -527,7 +535,7 @@ private void updateEventMask() { private int sendEncryptedData() throws IOException { this.session.getLock().lock(); try { - if (!this.outEncrypted.hasData()) { + 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, @@ -719,6 +727,8 @@ public void close(final CloseMode closeMode) { // 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); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/ssl/TransportSecurityLayer.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/ssl/TransportSecurityLayer.java index 4c48aea921..3b2803057a 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/ssl/TransportSecurityLayer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/ssl/TransportSecurityLayer.java @@ -51,6 +51,7 @@ public interface TransportSecurityLayer { * @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, @@ -71,7 +72,7 @@ void startTls( * @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( @@ -89,7 +90,7 @@ default void startTls( } /** - * Returns details of a fully established TLS session. + * Gets the details of a fully established TLS session. * * @return TLS session details. */ diff --git a/httpcore5/src/main/java/org/apache/hc/core5/ssl/SSLContextBuilder.java b/httpcore5/src/main/java/org/apache/hc/core5/ssl/SSLContextBuilder.java index 710ae87103..ec7e7377a3 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/ssl/SSLContextBuilder.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/ssl/SSLContextBuilder.java @@ -124,7 +124,7 @@ public SSLContextBuilder() { * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext">Java * Cryptography Architecture Standard Algorithm Name * Documentation for more information. - * @return this builder + * @return this instance. * @see Java * Cryptography Architecture Standard Algorithm Name Documentation @@ -146,8 +146,9 @@ public SSLContextBuilder setProvider(final String name) { /** * Sets the JCA provider to use for creating trust stores. + * * @param provider provider to use for creating trust stores. - * @return this builder + * @return this instance. * @since 5.2 */ public SSLContextBuilder setTrustStoreProvider(final Provider provider) { @@ -157,8 +158,10 @@ public SSLContextBuilder setTrustStoreProvider(final Provider provider) { /** * 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 builder + * @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 { @@ -168,8 +171,9 @@ public SSLContextBuilder setTrustStoreProvider(final String name) throws NoSuchP /** * Sets the JCA provider to use for creating key stores. + * * @param provider provider to use for creating key stores. - * @return this builder + * @return this instance. * @since 5.2 */ public SSLContextBuilder setKeyStoreProvider(final Provider provider) { @@ -179,8 +183,10 @@ public SSLContextBuilder setKeyStoreProvider(final Provider provider) { /** * 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 builder + * @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 { @@ -197,7 +203,7 @@ public SSLContextBuilder setKeyStoreProvider(final String name) throws NoSuchPro * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyStore">Java * Cryptography Architecture Standard Algorithm Name * Documentation for more information. - * @return this builder + * @return this instance. * @see Java * Cryptography Architecture Standard Algorithm Name Documentation @@ -217,7 +223,7 @@ public SSLContextBuilder setKeyStoreType(final String keyStoreType) { * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyManagerFactory">Java * Cryptography Architecture Standard Algorithm Name * Documentation for more information. - * @return this builder + * @return this instance. * @see Java * Cryptography Architecture Standard Algorithm Name Documentation @@ -237,7 +243,7 @@ public SSLContextBuilder setKeyManagerFactoryAlgorithm(final String keyManagerFa * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#TrustManagerFactory">Java * Cryptography Architecture Standard Algorithm Name * Documentation for more information. - * @return this builder + * @return this instance. * @see Java * Cryptography Architecture Standard Algorithm Name Documentation @@ -257,6 +263,9 @@ public SSLContextBuilder setSecureRandom(final SecureRandom secureRandom) { * @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, @@ -285,6 +294,11 @@ public SSLContextBuilder loadTrustMaterial( } /** + * @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( @@ -293,6 +307,11 @@ public SSLContextBuilder loadTrustMaterial( } /** + * @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( @@ -302,6 +321,11 @@ public SSLContextBuilder loadTrustMaterial( } /** + * @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( @@ -380,6 +404,12 @@ public SSLContextBuilder loadKeyMaterial( } /** + * @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( @@ -391,6 +421,12 @@ public SSLContextBuilder loadKeyMaterial( } /** + * @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( @@ -441,6 +477,9 @@ public SSLContextBuilder loadKeyMaterial( return loadKeyMaterial(url, storePassword, keyPassword, null); } + /** + * @throws KeyManagementException if this SSLContext operation fails. + */ protected void initSSLContext( final SSLContext sslContext, final Collection keyManagers, @@ -452,6 +491,12 @@ protected void initSSLContext( 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); @@ -470,6 +515,13 @@ private KeyStore loadKeyStore(final URL url, final char[] 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; @@ -602,6 +654,9 @@ public String chooseEngineServerAlias( } + /** + * @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) { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/util/Args.java b/httpcore5/src/main/java/org/apache/hc/core5/util/Args.java index 94e5f6bd39..448bd3ae43 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/util/Args.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/util/Args.java @@ -103,10 +103,6 @@ private static IllegalArgumentException illegalArgumentExceptionNotEmpty(final S return new IllegalArgumentException(name + " must not be empty"); } - private static NullPointerException NullPointerException(final String name) { - return new NullPointerException(name + " must not be null"); - } - public static T notBlank(final T argument, final String name) { notNull(argument, name); if (TextUtils.isBlank(argument)) { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/util/Deadline.java b/httpcore5/src/main/java/org/apache/hc/core5/util/Deadline.java index fb9861420a..efbaaed950 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/util/Deadline.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/util/Deadline.java @@ -183,9 +183,8 @@ public int hashCode() { public String format(final TimeUnit overdueTimeUnit) { if (value == MAX_VALUE.value) { return "No deadline (infinite)"; - } else { - return String.format("Deadline: %s, %s overdue", formatTarget(), TimeValue.of(remaining(), overdueTimeUnit)); } + return String.format("Deadline: %s, %s overdue", formatTarget(), TimeValue.of(remaining(), overdueTimeUnit)); } /** @@ -196,9 +195,8 @@ public String format(final TimeUnit overdueTimeUnit) { public String formatTarget() { if (value == MAX_VALUE.value) { return "(infinite)"; - } else { - return DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(value).atOffset(ZoneOffset.UTC)); } + return DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(value).atOffset(ZoneOffset.UTC)); } public Deadline freeze() { diff --git a/httpcore5/src/main/java/org/apache/hc/core5/util/ReflectionUtils.java b/httpcore5/src/main/java/org/apache/hc/core5/util/ReflectionUtils.java index 2c5f72cd7b..749268dbae 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/util/ReflectionUtils.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/util/ReflectionUtils.java @@ -28,8 +28,6 @@ package org.apache.hc.core5.util; import java.lang.reflect.Method; -import java.security.AccessController; -import java.security.PrivilegedAction; import org.apache.hc.core5.annotation.Internal; @@ -40,30 +38,40 @@ public static void callSetter(final Object object, final String setterName, fina try { final Class clazz = object.getClass(); final Method method = clazz.getMethod("set" + setterName, type); - setAccessible(method); + 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 = clazz.getMethod("get" + getterName); - setAccessible(method); - return resultType.cast(method.invoke(object)); + 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; } } - private static void setAccessible(final Method method) { - AccessController.doPrivileged((PrivilegedAction) () -> { - method.setAccessible(true); - return null; - }); - } - public static int determineJRELevel() { final String s = System.getProperty("java.version"); final String[] parts = s.split("\\."); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/util/TextUtils.java b/httpcore5/src/main/java/org/apache/hc/core5/util/TextUtils.java index db4c948bc9..73864f0a05 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/util/TextUtils.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/util/TextUtils.java @@ -32,6 +32,8 @@ import org.apache.hc.core5.annotation.Internal; /** + * Tests and converts Strings and CharSequence. + * * @since 4.3 */ public final class TextUtils { @@ -41,7 +43,10 @@ private TextUtils() { } /** - * Returns true if the parameter is null or of zero length + * 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; @@ -91,6 +96,10 @@ public static int length(final CharSequence cs) { } /** + * 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) { @@ -98,7 +107,7 @@ public static boolean containsBlanks(final CharSequence s) { if (strLen == 0) { return false; } - for (int i = 0; i < s.length(); i++) { + for (int i = 0; i < strLen; i++) { if (Character.isWhitespace(s.charAt(i))) { return true; } @@ -119,9 +128,9 @@ public static String toHexString(final byte[] bytes) { if (bytes == null) { return null; } - final StringBuilder buffer = new StringBuilder(); - for (int i = 0; i < bytes.length; i++) { - final int unsignedB = bytes[i] & 0xff; + final StringBuilder buffer = new StringBuilder(bytes.length * 2); + for (final byte element : bytes) { + final int unsignedB = element & 0xff; if (unsignedB < 16) { buffer.append('0'); } @@ -131,9 +140,10 @@ public static String toHexString(final byte[] bytes) { } /** - * Returns lower case representation of the given string - * using {@link Locale#ROOT}. + * 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) { @@ -143,10 +153,30 @@ public static String toLowerCase(final String s) { 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 + * 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 @@ -155,9 +185,8 @@ public static byte castAsByte(final int c) { (c >= 0xA0 && c <= 0xFF) || // Visible ISO-8859-1 c == 0x09) { // TAB return (byte) c; - } else { - return '?'; } + return '?'; } } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/util/TimeValue.java b/httpcore5/src/main/java/org/apache/hc/core5/util/TimeValue.java index a947dba750..dd0e994286 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/util/TimeValue.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/util/TimeValue.java @@ -91,6 +91,7 @@ public static int asBoundInt(final long 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} diff --git a/httpcore5/src/main/java/org/apache/hc/core5/util/Timeout.java b/httpcore5/src/main/java/org/apache/hc/core5/util/Timeout.java index 9199fc9276..f7766455df 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/util/Timeout.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/util/Timeout.java @@ -57,6 +57,13 @@ public class Timeout extends TimeValue { */ 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}. * @@ -67,6 +74,18 @@ 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. * diff --git a/httpcore5/src/main/java/org/apache/hc/core5/util/Tokenizer.java b/httpcore5/src/main/java/org/apache/hc/core5/util/Tokenizer.java index 5c9b861ab5..5807eb9f92 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/util/Tokenizer.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/util/Tokenizer.java @@ -96,6 +96,10 @@ public String toString() { } + /** + * @deprecated Do not use. + */ + @Deprecated public static BitSet INIT_BITSET(final int ... b) { final BitSet bitset = new BitSet(); for (final int aB : b) { @@ -119,6 +123,60 @@ 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(); /** @@ -127,34 +185,42 @@ public static boolean isWhitespace(final char ch) { * * @param buf buffer with the sequence of chars to be parsed * @param cursor defines the bounds and current position of the buffer - * @param delimiters set of delimiting characters. Can be {@code null} if the token + * @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 BitSet delimiters) { + 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, delimiters, dst); + 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 delimiters set of delimiting characters. Can be {@code null} if the token + * @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 BitSet delimiters) { + 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 (delimiters != null && delimiters.get(current)) { + if (delimiterPredicate != null && delimiterPredicate.test(current)) { break; } else if (isWhitespace(current)) { skipWhiteSpace(buf, cursor); @@ -163,13 +229,21 @@ public String parseToken(final CharSequence buf, final Cursor cursor, final BitS if (whitespace && dst.length() > 0) { dst.append(' '); } - copyContent(buf, cursor, delimiters, dst); + 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 @@ -177,17 +251,17 @@ public String parseToken(final CharSequence buf, final Cursor cursor, final BitS * * @param buf buffer with the sequence of chars to be parsed * @param cursor defines the bounds and current position of the buffer - * @param delimiters set of delimiting characters. Can be {@code null} if the value + * @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 BitSet delimiters) { + 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 (delimiters != null && delimiters.get(current)) { + if (delimiterPredicate != null && delimiterPredicate.test(current)) { break; } else if (isWhitespace(current)) { skipWhiteSpace(buf, cursor); @@ -202,13 +276,21 @@ public String parseValue(final CharSequence buf, final Cursor cursor, final BitS if (whitespace && dst.length() > 0) { dst.append(' '); } - copyUnquotedContent(buf, cursor, delimiters, dst); + 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. @@ -238,11 +320,11 @@ public void skipWhiteSpace(final CharSequence buf, final Cursor cursor) { * * @param buf buffer with the sequence of chars to be parsed * @param cursor defines the bounds and current position of the buffer - * @param delimiters set of delimiting characters. Can be {@code null} if the value + * @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 BitSet delimiters, + 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"); @@ -252,7 +334,7 @@ public void copyContent(final CharSequence buf, final Cursor cursor, final BitSe final int indexTo = cursor.getUpperBound(); for (int i = indexFrom; i < indexTo; i++) { final char current = buf.charAt(i); - if ((delimiters != null && delimiters.get(current)) || isWhitespace(current)) { + if ((delimiterPredicate != null && delimiterPredicate.test(current)) || isWhitespace(current)) { break; } pos++; @@ -261,18 +343,27 @@ public void copyContent(final CharSequence buf, final Cursor cursor, final BitSe 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 delimiters set of delimiting characters. Can be {@code null} if the value + * @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 BitSet delimiters, final StringBuilder dst) { + final Delimiter delimiterPredicate, final StringBuilder dst) { Args.notNull(buf, "Char sequence"); Args.notNull(cursor, "Parser cursor"); Args.notNull(dst, "String builder"); @@ -281,7 +372,7 @@ public void copyUnquotedContent(final CharSequence buf, final Cursor cursor, final int indexTo = cursor.getUpperBound(); for (int i = indexFrom; i < indexTo; i++) { final char current = buf.charAt(i); - if ((delimiters != null && delimiters.get(current)) + if ((delimiterPredicate != null && delimiterPredicate.test(current)) || isWhitespace(current) || current == DQUOTE) { break; } @@ -291,6 +382,15 @@ public void copyUnquotedContent(final CharSequence buf, final Cursor cursor, 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. * diff --git a/httpcore5/src/main/java/org/apache/hc/core5/util/VersionInfo.java b/httpcore5/src/main/java/org/apache/hc/core5/util/VersionInfo.java index 7801efba4d..edcb6b5663 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/util/VersionInfo.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/util/VersionInfo.java @@ -261,7 +261,7 @@ public static VersionInfo loadVersionInfo(final String pckg, final ClassLoader c * * @param pckg the package for the version information * @param info the map from string keys to string values, - * for example {@link java.util.Properties} + * for example {@link Properties} * @param clsldr the classloader, or {@code null} * * @return the version information diff --git a/httpcore5/src/test/java/org/apache/hc/core5/annotation/ThreadingBehaviorTest.java b/httpcore5/src/test/java/org/apache/hc/core5/annotation/ThreadingBehaviorTest.java index 2ed0b149b3..c2ab2154b0 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/annotation/ThreadingBehaviorTest.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/annotation/ThreadingBehaviorTest.java @@ -31,7 +31,7 @@ import org.junit.jupiter.api.Test; -public class ThreadingBehaviorTest { +class ThreadingBehaviorTest { @Test void testName(){ diff --git a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/CountDownLatchFutureCallback.java b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/CountDownLatchFutureCallback.java new file mode 100644 index 0000000000..d6934a3710 --- /dev/null +++ b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/CountDownLatchFutureCallback.java @@ -0,0 +1,95 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by 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(); + } + +} diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestUriPatternType.java b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/CountingFutureCallback.java similarity index 56% rename from httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestUriPatternType.java rename to httpcore5/src/test/java/org/apache/hc/core5/concurrent/CountingFutureCallback.java index 99f19d5182..e141988b20 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestUriPatternType.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/CountingFutureCallback.java @@ -25,29 +25,50 @@ * */ -package org.apache.hc.core5.http.protocol; +package org.apache.hc.core5.concurrent; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; +import java.util.concurrent.atomic.AtomicLong; -public class TestUriPatternType { +/** + * 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(); + } - @Test - public void testRegex() { - final LookupRegistry matcher = UriPatternType.newMatcher(UriPatternType.REGEX); - Assertions.assertTrue(matcher instanceof UriRegexMatcher); + @Override + public void failed(final Exception ex) { + count.incrementAndGet(); } - @Test - public void testUriPattern() { - final LookupRegistry matcher = UriPatternType.newMatcher(UriPatternType.URI_PATTERN); - Assertions.assertTrue(matcher instanceof UriPatternMatcher); + /** + * The atomic count as an AtomicLong. + * + * @return atomic count as an AtomicLong. + */ + public AtomicLong getAtomicCount() { + return count; } - @Test - public void testUriPatternInOrder() { - final LookupRegistry matcher = UriPatternType.newMatcher(UriPatternType.URI_PATTERN_IN_ORDER); - Assertions.assertTrue(matcher instanceof UriPatternOrderedMatcher); + /** + * The atomic count as a long. + * + * @return atomic count as a long. + */ + public long getCount() { + return count.longValue(); } } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/DefaultThreadFactoryTest.java b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/DefaultThreadFactoryTest.java index 7ffc392f43..aff64aff57 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/DefaultThreadFactoryTest.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/DefaultThreadFactoryTest.java @@ -33,7 +33,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class DefaultThreadFactoryTest { +class DefaultThreadFactoryTest { @Test void newThread() throws Exception { @@ -42,7 +42,7 @@ void newThread() throws Exception { final Thread thread = defaultThreadFactory.newThread(lockHeld::countDown); Assertions.assertNotNull(thread); thread.start(); - Assertions.assertTrue(lockHeld.await(100, TimeUnit.MILLISECONDS)); + Assertions.assertTrue(lockHeld.await(15, TimeUnit.SECONDS)); } } \ No newline at end of file diff --git a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/FutureCallbackAdapter.java b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/FutureCallbackAdapter.java new file mode 100644 index 0000000000..f5b740300e --- /dev/null +++ b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/FutureCallbackAdapter.java @@ -0,0 +1,67 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by 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 + } + +} diff --git a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestBasicFuture.java b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestBasicFuture.java index a95e3a809d..432ea16da2 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestBasicFuture.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestBasicFuture.java @@ -26,20 +26,31 @@ */ 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; -public class TestBasicFuture { +class TestBasicFuture { @Test - public void testCompleted() throws Exception { + void testCompleted() throws Exception { final FutureCallback callback = Mockito.mock(FutureCallback.class); final BasicFuture future = new BasicFuture<>(callback); @@ -50,7 +61,7 @@ public void testCompleted() throws Exception { future.completed(result); future.failed(boom); Mockito.verify(callback).completed(result); - Mockito.verify(callback, Mockito.never()).failed(Mockito.any()); + Mockito.verify(callback, Mockito.never()).failed(ArgumentMatchers.any()); Mockito.verify(callback, Mockito.never()).cancelled(); Assertions.assertSame(result, future.get()); @@ -60,7 +71,7 @@ public void testCompleted() throws Exception { } @Test - public void testCompletedWithTimeout() throws Exception { + void testCompletedWithTimeout() throws Exception { final FutureCallback callback = Mockito.mock(FutureCallback.class); final BasicFuture future = new BasicFuture<>(callback); @@ -71,7 +82,7 @@ public void testCompletedWithTimeout() throws Exception { future.completed(result); future.failed(boom); Mockito.verify(callback).completed(result); - Mockito.verify(callback, Mockito.never()).failed(Mockito.any()); + Mockito.verify(callback, Mockito.never()).failed(ArgumentMatchers.any()); Mockito.verify(callback, Mockito.never()).cancelled(); Assertions.assertSame(result, future.get(1, TimeUnit.MILLISECONDS)); @@ -80,14 +91,14 @@ public void testCompletedWithTimeout() throws Exception { } @Test - public void testFailed() throws Exception { + 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(Mockito.any()); + Mockito.verify(callback, Mockito.never()).completed(ArgumentMatchers.any()); Mockito.verify(callback).failed(boom); Mockito.verify(callback, Mockito.never()).cancelled(); @@ -101,7 +112,7 @@ public void testFailed() throws Exception { } @Test - public void testCancelled() throws Exception { + void testCancelled() { final FutureCallback callback = Mockito.mock(FutureCallback.class); final BasicFuture future = new BasicFuture<>(callback); final Object result = new Object(); @@ -109,17 +120,17 @@ public void testCancelled() throws Exception { future.cancel(true); future.failed(boom); future.completed(result); - Mockito.verify(callback, Mockito.never()).completed(Mockito.any()); - Mockito.verify(callback, Mockito.never()).failed(Mockito.any()); + Mockito.verify(callback, Mockito.never()).completed(ArgumentMatchers.any()); + Mockito.verify(callback, Mockito.never()).failed(ArgumentMatchers.any()); Mockito.verify(callback).cancelled(); - Assertions.assertThrows(CancellationException.class, future::get); + assertThrows(CancellationException.class, future::get); Assertions.assertTrue(future.isDone()); Assertions.assertTrue(future.isCancelled()); } @Test - public void testAsyncCompleted() throws Exception { + void testAsyncCompleted() throws Exception { final BasicFuture future = new BasicFuture<>(null); final Object result = new Object(); @@ -138,7 +149,7 @@ public void testAsyncCompleted() throws Exception { } @Test - public void testAsyncFailed() throws Exception { + void testAsyncFailed() throws Exception { final BasicFuture future = new BasicFuture<>(null); final Exception boom = new Exception(); @@ -161,7 +172,7 @@ public void testAsyncFailed() throws Exception { } @Test - public void testAsyncCancelled() throws Exception { + void testAsyncCancelled() { final BasicFuture future = new BasicFuture<>(null); final Thread t = new Thread(() -> { @@ -173,12 +184,12 @@ public void testAsyncCancelled() throws Exception { }); t.setDaemon(true); t.start(); - Assertions.assertThrows(CancellationException.class, () -> + assertThrows(CancellationException.class, () -> future.get(60, TimeUnit.SECONDS)); } @Test - public void testAsyncTimeout() throws Exception { + void testAsyncTimeout() { final BasicFuture future = new BasicFuture<>(null); final Object result = new Object(); @@ -191,15 +202,82 @@ public void testAsyncTimeout() throws Exception { }); t.setDaemon(true); t.start(); - Assertions.assertThrows(TimeoutException.class, () -> + assertThrows(TimeoutException.class, () -> future.get(1, TimeUnit.MILLISECONDS)); } @Test - public void testAsyncNegativeTimeout() throws Exception { + void testAsyncNegativeTimeout() { final BasicFuture future = new BasicFuture<>(null); - Assertions.assertThrows(TimeoutValueException.class, () -> + 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()); + } + }); + } } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestCompletedFuture.java b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestCompletedFuture.java index 93768fb178..df65f57957 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestCompletedFuture.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestCompletedFuture.java @@ -29,10 +29,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestCompletedFuture { +class TestCompletedFuture { @Test - public void testCompleted() { + void testCompleted() { final Object result = new Object(); final CompletedFuture future = new CompletedFuture<>(result); Assertions.assertSame(result, future.get()); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestComplexCancellable.java b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestComplexCancellable.java index 928e88964b..42806b9b84 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestComplexCancellable.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestComplexCancellable.java @@ -32,10 +32,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestComplexCancellable { +class TestComplexCancellable { @Test - public void testCancelled() throws Exception { + void testCancelled() { final ComplexCancellable cancellable = new ComplexCancellable(); final BasicFuture dependency1 = new BasicFuture<>(null); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestComplexFuture.java b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestComplexFuture.java index cce22d653f..e50826b874 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestComplexFuture.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestComplexFuture.java @@ -34,10 +34,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestComplexFuture { +class TestComplexFuture { @Test - public void testCancelled() throws Exception { + void testCancelled() { final ComplexFuture future = new ComplexFuture<>(null); final Future dependency1 = new BasicFuture<>(null); @@ -55,7 +55,7 @@ public void testCancelled() throws Exception { } @Test - public void testCompleted() throws Exception { + void testCompleted() { final ComplexFuture future = new ComplexFuture<>(null); final Future dependency1 = new BasicFuture<>(null); @@ -73,7 +73,7 @@ public void testCompleted() throws Exception { } @Test - public void testCancelledWithCallback() throws Exception { + void testCancelledWithCallback() { final ComplexFuture future = new ComplexFuture<>(null); final Future dependency1 = new BasicFuture<>(new FutureContribution(future) { @@ -97,7 +97,7 @@ public void completed(final Object result) { } @Test - public void testCanceledAndFailed() { + void testCanceledAndFailed() { final ComplexFuture future = new ComplexFuture<>(null); assertThat(future.cancel(), CoreMatchers.is(true)); assertThat(future.failed(new Exception()), CoreMatchers.is(false)); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/TestContentType.java b/httpcore5/src/test/java/org/apache/hc/core5/http/TestContentType.java index d4f7e9fa0a..cea1eae592 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/TestContentType.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/TestContentType.java @@ -38,10 +38,10 @@ * Unit tests for {@link ContentType}. * */ -public class TestContentType { +class TestContentType { @Test - public void testBasis() throws Exception { + 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()); @@ -49,7 +49,7 @@ public void testBasis() throws Exception { } @Test - public void testWithCharset() throws Exception { + 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()); @@ -61,7 +61,7 @@ public void testWithCharset() throws Exception { } @Test - public void testWithCharsetString() throws Exception { + 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()); @@ -73,14 +73,14 @@ public void testWithCharsetString() throws Exception { } @Test - public void testLowCaseText() throws Exception { + 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 - public void testCreateInvalidInput() throws Exception { + 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)); @@ -88,7 +88,7 @@ public void testCreateInvalidInput() throws Exception { } @Test - public void testParse() throws Exception { + 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()); @@ -96,7 +96,7 @@ public void testParse() throws Exception { } @Test - public void testParseMultiparam() throws Exception { + 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()); @@ -110,14 +110,14 @@ public void testParseMultiparam() throws Exception { } @Test - public void testParseEmptyCharset() throws Exception { + void testParseEmptyCharset() throws Exception { final ContentType contentType = ContentType.parse("text/plain; charset=\" \""); Assertions.assertEquals("text/plain", contentType.getMimeType()); Assertions.assertNull(contentType.getCharset()); } @Test - public void testParseDefaultCharset() throws Exception { + void testParseDefaultCharset() throws Exception { final ContentType contentType = ContentType.parse("text/plain; charset=\" \""); Assertions.assertEquals("text/plain", contentType.getMimeType()); Assertions.assertNull(contentType.getCharset()); @@ -129,7 +129,7 @@ public void testParseDefaultCharset() throws Exception { } @Test - public void testParseEmptyValue() throws Exception { + void testParseEmptyValue() throws Exception { Assertions.assertNull(ContentType.parse(null)); Assertions.assertNull(ContentType.parse("")); Assertions.assertNull(ContentType.parse(" ")); @@ -138,7 +138,7 @@ public void testParseEmptyValue() throws Exception { } @Test - public void testWithParamArrayChange() throws Exception { + void testWithParamArrayChange() throws Exception { final BasicNameValuePair[] params = {new BasicNameValuePair("charset", "UTF-8"), new BasicNameValuePair("p", "this"), new BasicNameValuePair("p", "that")}; @@ -154,7 +154,7 @@ public void testWithParamArrayChange() throws Exception { } @Test - public void testWithParams() throws Exception { + void testWithParams() throws Exception { ContentType contentType = ContentType.create("text/plain", new BasicNameValuePair("charset", "UTF-8"), new BasicNameValuePair("p", "this"), diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/TestHttpExceptions.java b/httpcore5/src/test/java/org/apache/hc/core5/http/TestHttpExceptions.java index 6262e7084f..29da8ab794 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/TestHttpExceptions.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/TestHttpExceptions.java @@ -33,56 +33,56 @@ /** * Simple tests for various HTTP exception classes. */ -public class TestHttpExceptions { +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 - public void testConstructor() { + void testConstructor() { final Throwable cause = new Exception(); - new HttpException(); - new HttpException("Oppsie"); - new HttpException("Oppsie", cause); - new ProtocolException(); - new ProtocolException("Oppsie"); - new ProtocolException("Oppsie", cause); - new NoHttpResponseException("Oppsie"); - new ConnectionClosedException("Oppsie"); - new MethodNotSupportedException("Oppsie"); - new MethodNotSupportedException("Oppsie", cause); - new UnsupportedHttpVersionException(); - new UnsupportedHttpVersionException("Oppsie"); + 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 - public void testNonPrintableCharactersInConnectionClosedException() { + void testNonPrintableCharactersInConnectionClosedException() { Assertions.assertEquals(CLEAN_MESSAGE, new ConnectionClosedException(nonPrintableMessage).getMessage()); } @Test - public void testNonPrintableCharactersInHttpException() { + void testNonPrintableCharactersInHttpException() { Assertions.assertEquals(CLEAN_MESSAGE, new HttpException(nonPrintableMessage).getMessage()); } @Test - public void testNonPrintableCharactersInMethodNotSupportedException() { + void testNonPrintableCharactersInMethodNotSupportedException() { Assertions.assertEquals(CLEAN_MESSAGE, new MethodNotSupportedException(nonPrintableMessage).getMessage()); } @Test - public void testNonPrintableCharactersInNoHttpResponseException() { + void testNonPrintableCharactersInNoHttpResponseException() { Assertions.assertEquals(CLEAN_MESSAGE, new NoHttpResponseException(nonPrintableMessage).getMessage()); } @Test - public void testNonPrintableCharactersInProtocolException() { + void testNonPrintableCharactersInProtocolException() { Assertions.assertEquals(CLEAN_MESSAGE, new ProtocolException(nonPrintableMessage).getMessage()); } @Test - public void testNonPrintableCharactersInUnsupportedHttpVersionException() { + void testNonPrintableCharactersInUnsupportedHttpVersionException() { Assertions.assertEquals(CLEAN_MESSAGE, new UnsupportedHttpVersionException(nonPrintableMessage).getMessage()); } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/TestHttpHost.java b/httpcore5/src/test/java/org/apache/hc/core5/http/TestHttpHost.java index c6032a130e..f7d16c6e97 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/TestHttpHost.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/TestHttpHost.java @@ -42,10 +42,10 @@ * Unit tests for {@link HttpHost}. * */ -public class TestHttpHost { +class TestHttpHost { @Test - public void testConstructor() { + void testConstructor() { final HttpHost host1 = new HttpHost("somehost"); Assertions.assertEquals("somehost", host1.getHostName()); Assertions.assertEquals(-1, host1.getPort()); @@ -72,7 +72,7 @@ public void testConstructor() { } @Test - public void testHashCode() throws Exception { + 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); @@ -90,20 +90,20 @@ public void testHashCode() throws Exception { "http", InetAddress.getByAddress("someotherhost",new byte[] {127,0,0,1}), 80); Assertions.assertEquals(host1.hashCode(), host1.hashCode()); - Assertions.assertTrue(host1.hashCode() != host2.hashCode()); - Assertions.assertTrue(host1.hashCode() != host3.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.assertTrue(host5.hashCode() != host6.hashCode()); - Assertions.assertTrue(host7.hashCode() != host8.hashCode()); - Assertions.assertTrue(host8.hashCode() != host9.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.assertTrue(host10.hashCode() != host11.hashCode()); - Assertions.assertTrue(host9.hashCode() != host11.hashCode()); + Assertions.assertNotEquals(host10.hashCode(), host11.hashCode()); + Assertions.assertNotEquals(host9.hashCode(), host11.hashCode()); } @Test - public void testEquals() throws Exception { + 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); @@ -127,7 +127,7 @@ public void testEquals() throws Exception { Assertions.assertEquals(host2, host5); Assertions.assertNotEquals(host5, host6); Assertions.assertNotEquals(host7, host8); - Assertions.assertFalse(host7.equals(host9)); + Assertions.assertNotEquals(host7, host9); Assertions.assertNotEquals(null, host1); Assertions.assertNotEquals("http://somehost", host1); Assertions.assertNotEquals("http://somehost", host9); @@ -137,7 +137,7 @@ public void testEquals() throws Exception { } @Test - public void testToString() throws Exception { + void testToString() throws Exception { final HttpHost host1 = new HttpHost("somehost"); Assertions.assertEquals("http://somehost", host1.toString()); final HttpHost host2 = new HttpHost("somehost", -1); @@ -159,7 +159,7 @@ public void testToString() throws Exception { } @Test - public void testToHostString() { + void testToHostString() { final HttpHost host1 = new HttpHost("somehost"); Assertions.assertEquals("somehost", host1.toHostString()); final HttpHost host2 = new HttpHost("somehost"); @@ -171,12 +171,12 @@ public void testToHostString() { } @Test - public void testSerialization() throws Exception { + void testSerialization() throws Exception { final HttpHost orig = new HttpHost("https", "somehost", 8080); final ByteArrayOutputStream outbuffer = new ByteArrayOutputStream(); - final ObjectOutputStream outStream = new ObjectOutputStream(outbuffer); - outStream.writeObject(orig); - outStream.close(); + 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); @@ -185,7 +185,7 @@ public void testSerialization() throws Exception { } @Test - public void testCreateFromString() throws Exception { + 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")); @@ -193,21 +193,21 @@ public void testCreateFromString() throws Exception { } @Test - public void testCreateFromURI() throws Exception { + 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 - public void testCreateFromStringInvalid() throws Exception { + void testCreateFromStringInvalid() { Assertions.assertThrows(URISyntaxException.class, () -> HttpHost.create(" host ")); Assertions.assertThrows(URISyntaxException.class, () -> HttpHost.create("host :8080")); Assertions.assertThrows(IllegalArgumentException.class, () -> HttpHost.create("")); } @Test - public void testIpv6HostAndPort() throws Exception { + void testIpv6HostAndPort() throws Exception { final HttpHost host = HttpHost.create("[::1]:80"); Assertions.assertEquals("http", host.getSchemeName()); Assertions.assertEquals("::1", host.getHostName()); @@ -215,7 +215,7 @@ public void testIpv6HostAndPort() throws Exception { } @Test - public void testIpv6HostAndPortWithScheme() throws Exception { + void testIpv6HostAndPortWithScheme() throws Exception { final HttpHost host = HttpHost.create("https://[::1]:80"); Assertions.assertEquals("https", host.getSchemeName()); Assertions.assertEquals("::1", host.getHostName()); @@ -223,17 +223,17 @@ public void testIpv6HostAndPortWithScheme() throws Exception { } @Test - public void testIpv6HostAndPortWithoutBrackets() throws Exception { + void testIpv6HostAndPortWithoutBrackets() { Assertions.assertThrows(URISyntaxException.class, () -> HttpHost.create("::1:80")); } @Test - public void testIpv6HostWithoutPort() throws Exception { + void testIpv6HostWithoutPort() { Assertions.assertThrows(URISyntaxException.class, () -> HttpHost.create("::1")); } @Test - public void testIpv6HostToString() { + void testIpv6HostToString() { Assertions.assertEquals("http://[::1]:80", new HttpHost("::1", 80).toString()); Assertions.assertEquals("http://[::1]", new HttpHost("::1", -1).toString()); } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/TestHttpVersion.java b/httpcore5/src/test/java/org/apache/hc/core5/http/TestHttpVersion.java index bd64ab44c6..70f17a2673 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/TestHttpVersion.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/TestHttpVersion.java @@ -38,10 +38,10 @@ /** * Test cases for HTTP version class */ -public class TestHttpVersion { +class TestHttpVersion { @Test - public void testEqualsMajorMinor() { + 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)); @@ -52,7 +52,7 @@ public void testEqualsMajorMinor() { } @Test - public void testGet() { + 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)); @@ -60,23 +60,23 @@ public void testGet() { Assertions.assertEquals(HttpVersion.HTTP_2, HttpVersion.get(2, 0)); Assertions.assertNotEquals(HttpVersion.HTTP_2_0, HttpVersion.get(2, 1)); // - Assertions.assertSame(HttpVersion.HTTP_0_9, HttpVersion.get(0, 9)); 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 - public void testHttpVersionInvalidConstructorInput() throws Exception { + void testHttpVersionInvalidConstructorInput() { Assertions.assertThrows(IllegalArgumentException.class, () -> new HttpVersion(-1, -1)); Assertions.assertThrows(IllegalArgumentException.class, () -> new HttpVersion(0, -1)); } @Test - public void testHttpVersionEquality() throws Exception { + void testHttpVersionEquality() { final HttpVersion ver1 = new HttpVersion(1, 1); final HttpVersion ver2 = new HttpVersion(1, 1); @@ -86,17 +86,17 @@ public void testHttpVersionEquality() throws Exception { Assertions.assertEquals(ver1, ver1); Assertions.assertEquals(ver1, ver2); - Assertions.assertFalse(ver1.equals(Float.valueOf(1.1f))); + Assertions.assertNotEquals(ver1, Float.valueOf(1.1f)); - Assertions.assertEquals((new HttpVersion(0, 9)), HttpVersion.HTTP_0_9); - Assertions.assertEquals((new HttpVersion(1, 0)), HttpVersion.HTTP_1_0); - Assertions.assertEquals((new HttpVersion(1, 1)), HttpVersion.HTTP_1_1); - Assertions.assertNotEquals((new HttpVersion(1, 1)), HttpVersion.HTTP_1_0); + 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((new ProtocolVersion("HTTP", 0, 9)), HttpVersion.HTTP_0_9); - Assertions.assertEquals((new ProtocolVersion("HTTP", 1, 0)), HttpVersion.HTTP_1_0); - Assertions.assertEquals((new ProtocolVersion("HTTP", 1, 1)), HttpVersion.HTTP_1_1); - Assertions.assertNotEquals((new ProtocolVersion("http", 1, 1)), HttpVersion.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))); Assertions.assertEquals(HttpVersion.HTTP_0_9, new ProtocolVersion("HTTP", 0, 9)); Assertions.assertEquals(HttpVersion.HTTP_1_0, new ProtocolVersion("HTTP", 1, 0)); @@ -105,7 +105,7 @@ public void testHttpVersionEquality() throws Exception { } @Test - public void testHttpVersionComparison() { + 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)); @@ -116,7 +116,7 @@ public void testHttpVersionComparison() { } @Test - public void testSerialization() throws Exception { + void testSerialization() throws Exception { final HttpVersion orig = HttpVersion.HTTP_1_1; final ByteArrayOutputStream outbuffer = new ByteArrayOutputStream(); try (final ObjectOutputStream outStream = new ObjectOutputStream(outbuffer)) { diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/TestProtocolVersion.java b/httpcore5/src/test/java/org/apache/hc/core5/http/TestProtocolVersion.java index 827e0a336c..91a4049bff 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/TestProtocolVersion.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/TestProtocolVersion.java @@ -27,17 +27,21 @@ 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; -public class TestProtocolVersion { +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 - public void testEqualsMajorMinor() { + 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)); @@ -45,4 +49,31 @@ public void testEqualsMajorMinor() { 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?")); + } + } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/TestProtocolVersionParser.java b/httpcore5/src/test/java/org/apache/hc/core5/http/TestProtocolVersionParser.java new file mode 100644 index 0000000000..501c5763ab --- /dev/null +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/TestProtocolVersionParser.java @@ -0,0 +1,117 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * 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")); + } + +} diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/config/TestNamedElementChain.java b/httpcore5/src/test/java/org/apache/hc/core5/http/config/TestNamedElementChain.java index e64fca9e39..50b6251ac2 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/config/TestNamedElementChain.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/config/TestNamedElementChain.java @@ -39,10 +39,10 @@ /** * Tests for {@link NamedElementChain}. */ -public class TestNamedElementChain { +class TestNamedElementChain { @Test - public void testBasics() { + void testBasics() { final NamedElementChain list = new NamedElementChain<>(); assertThat(list.getFirst(), CoreMatchers.nullValue()); assertThat(list.getLast(), CoreMatchers.nullValue()); @@ -100,7 +100,7 @@ public void testBasics() { } @Test - public void testFind() { + void testFind() { final NamedElementChain list = new NamedElementChain<>(); list.addLast('c', "c"); assertThat(list.find("c"), notNullValue()); @@ -108,7 +108,7 @@ public void testFind() { } @Test - public void testReplace() { + void testReplace() { final NamedElementChain list = new NamedElementChain<>(); list.addLast('c', "c"); final boolean found = list.replace("c",'z' ); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/config/TestRegistry.java b/httpcore5/src/test/java/org/apache/hc/core5/http/config/TestRegistry.java index 84a7a4176b..b0492f4af6 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/config/TestRegistry.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/config/TestRegistry.java @@ -29,10 +29,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestRegistry { +class TestRegistry { @Test - public void testCompleted() throws Exception { + void testCompleted() { final Registry reg = RegistryBuilder.create().register("Stuff", "Stuff").build(); Assertions.assertEquals("Stuff", reg.lookup("Stuff")); Assertions.assertEquals("Stuff", reg.lookup("stuff")); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncClientSNIExample.java b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncClientSNIExample.java new file mode 100644 index 0000000000..5ae6f4d87e --- /dev/null +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncClientSNIExample.java @@ -0,0 +1,154 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * 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(); + } + +} diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncFileServerExample.java b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncFileServerExample.java index bfc711544e..82c85af000 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncFileServerExample.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncFileServerExample.java @@ -102,7 +102,8 @@ public AsyncRequestConsumer> prepare( public void handle( final Message message, final ResponseTrigger responseTrigger, - final HttpContext context) throws HttpException, IOException { + final HttpContext localContext) throws HttpException, IOException { + final HttpCoreContext context = HttpCoreContext.cast(localContext); final HttpRequest request = message.getHead(); final URI requestUri; try { @@ -145,8 +146,7 @@ public void handle( contentType = ContentType.DEFAULT_BINARY; } - final HttpCoreContext coreContext = HttpCoreContext.adapt(context); - final EndpointDetails endpoint = coreContext.getEndpointDetails(); + final EndpointDetails endpoint = context.getEndpointDetails(); println(endpoint + " | serving file " + file.getPath()); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncReverseProxyExample.java b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncReverseProxyExample.java index 6a96b5d973..c1576b1713 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncReverseProxyExample.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/AsyncReverseProxyExample.java @@ -40,6 +40,7 @@ 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; @@ -253,12 +254,14 @@ private static class IncomingExchangeHandler implements AsyncServerExchangeHandl 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 @@ -268,7 +271,8 @@ public void handleRequest( final ResponseChannel responseChannel, final HttpContext httpContext) throws HttpException, IOException { - synchronized (exchangeState) { + lock.lock(); + try { println("[client->proxy] " + exchangeState.id + " " + incomingRequest.getMethod() + " " + incomingRequest.getRequestUri()); exchangeState.request = incomingRequest; @@ -282,6 +286,8 @@ public void handleRequest( responseChannel.sendInformation(new BasicHttpResponse(HttpStatus.SC_CONTINUE), httpContext); } } + } finally { + lock.unlock(); } println("[proxy->origin] " + exchangeState.id + " request connection to " + targetHost); @@ -291,8 +297,11 @@ public void handleRequest( @Override public void completed(final AsyncClientEndpoint clientEndpoint) { println("[proxy->origin] " + exchangeState.id + " connection leased"); - synchronized (exchangeState) { + lock.lock(); + try { exchangeState.clientEndpoint = clientEndpoint; + } finally { + lock.unlock(); } clientEndpoint.execute( new OutgoingExchangeHandler(targetHost, clientEndpoint, exchangeState), @@ -306,12 +315,15 @@ public void failed(final Exception cause) { final ByteBuffer msg = StandardCharsets.US_ASCII.encode(CharBuffer.wrap(cause.getMessage())); final EntityDetails exEntityDetails = new BasicEntityDetails(msg.remaining(), ContentType.TEXT_PLAIN); - synchronized (exchangeState) { + 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()); @@ -333,19 +345,23 @@ public void cancelled() { @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { - synchronized (exchangeState) { + 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 { - synchronized (exchangeState) { + lock.lock(); + try { println("[client->proxy] " + exchangeState.id + " " + src.remaining() + " bytes received"); final DataStreamChannel dataChannel = exchangeState.requestDataChannel; if (dataChannel != null && exchangeState.inBuf != null) { @@ -369,12 +385,15 @@ public void consume(final ByteBuffer src) throws IOException { if (dataChannel != null) { dataChannel.requestOutput(); } + } finally { + lock.unlock(); } } @Override public void streamEnd(final List trailers) throws HttpException, IOException { - synchronized (exchangeState) { + lock.lock(); + try { println("[client->proxy] " + exchangeState.id + " end of input"); exchangeState.inputEnd = true; final DataStreamChannel dataChannel = exchangeState.requestDataChannel; @@ -382,21 +401,27 @@ public void streamEnd(final List trailers) throws HttpExceptio println("[proxy->origin] " + exchangeState.id + " end of output"); dataChannel.endStream(); } + } finally { + lock.unlock(); } } @Override public int available() { - synchronized (exchangeState) { + 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 { - synchronized (exchangeState) { + lock.lock(); + try { println("[client<-proxy] " + exchangeState.id + " produce output"); exchangeState.responseDataChannel = channel; @@ -420,6 +445,8 @@ public void produce(final DataStreamChannel channel) throws IOException { } } } + } finally { + lock.unlock(); } } @@ -429,19 +456,25 @@ public void failed(final Exception cause) { if (!(cause instanceof ConnectionClosedException)) { cause.printStackTrace(System.out); } - synchronized (exchangeState) { + lock.lock(); + try { if (exchangeState.clientEndpoint != null) { exchangeState.clientEndpoint.releaseAndDiscard(); } + } finally { + lock.unlock(); } } @Override public void releaseResources() { - synchronized (exchangeState) { + lock.lock(); + try { exchangeState.responseMessageChannel = null; exchangeState.responseDataChannel = null; exchangeState.requestCapacityChannel = null; + } finally { + lock.unlock(); } } @@ -463,6 +496,7 @@ private static class OutgoingExchangeHandler implements AsyncClientExchangeHandl private final HttpHost targetHost; private final AsyncClientEndpoint clientEndpoint; private final ProxyExchangeState exchangeState; + private final ReentrantLock lock; OutgoingExchangeHandler( final HttpHost targetHost, @@ -471,12 +505,14 @@ private static class OutgoingExchangeHandler implements AsyncClientExchangeHandl 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 { - synchronized (exchangeState) { + lock.lock(); + try { final HttpRequest incomingRequest = exchangeState.request; final EntityDetails entityDetails = exchangeState.requestEntityDetails; final HttpRequest outgoingRequest = new BasicHttpRequest( @@ -494,21 +530,27 @@ public void produceRequest( outgoingRequest.getMethod() + " " + outgoingRequest.getRequestUri()); channel.sendRequest(outgoingRequest, entityDetails, httpContext); + } finally { + lock.unlock(); } } @Override public int available() { - synchronized (exchangeState) { + 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 { - synchronized (exchangeState) { + lock.lock(); + try { println("[proxy->origin] " + exchangeState.id + " produce output"); exchangeState.requestDataChannel = channel; if (exchangeState.inBuf != null) { @@ -531,6 +573,8 @@ public void produce(final DataStreamChannel channel) throws IOException { } } } + } finally { + lock.unlock(); } } @@ -543,7 +587,8 @@ public void consumeInformation(final HttpResponse response, final HttpContext ht public void consumeResponse( final HttpResponse incomingResponse, final EntityDetails entityDetails, final HttpContext httpContext) throws HttpException, IOException { - synchronized (exchangeState) { + lock.lock(); + try { println("[proxy<-origin] " + exchangeState.id + " status " + incomingResponse.getCode()); if (entityDetails == null) { println("[proxy<-origin] " + exchangeState.id + " end of input"); @@ -572,24 +617,30 @@ public void consumeResponse( println("[client<-proxy] " + exchangeState.id + " end of output"); clientEndpoint.releaseAndReuse(); } + } finally { + lock.unlock(); } } @Override public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { - synchronized (exchangeState) { + 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 { - synchronized (exchangeState) { + lock.lock(); + try { println("[proxy<-origin] " + exchangeState.id + " " + src.remaining() + " bytes received"); final DataStreamChannel dataChannel = exchangeState.responseDataChannel; if (dataChannel != null && exchangeState.outBuf != null) { @@ -613,12 +664,15 @@ public void consume(final ByteBuffer src) throws IOException { if (dataChannel != null) { dataChannel.requestOutput(); } + } finally { + lock.unlock(); } } @Override public void streamEnd(final List trailers) throws HttpException, IOException { - synchronized (exchangeState) { + lock.lock(); + try { println("[proxy<-origin] " + exchangeState.id + " end of input"); exchangeState.outputEnd = true; final DataStreamChannel dataChannel = exchangeState.responseDataChannel; @@ -627,6 +681,8 @@ public void streamEnd(final List trailers) throws HttpExceptio dataChannel.endStream(); clientEndpoint.releaseAndReuse(); } + } finally { + lock.unlock(); } } @@ -641,7 +697,8 @@ public void failed(final Exception cause) { if (!(cause instanceof ConnectionClosedException)) { cause.printStackTrace(System.out); } - synchronized (exchangeState) { + 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); @@ -666,15 +723,20 @@ public void failed(final Exception cause) { exchangeState.outputEnd = true; } clientEndpoint.releaseAndDiscard(); + } finally { + lock.unlock(); } } @Override public void releaseResources() { - synchronized (exchangeState) { + lock.lock(); + try { exchangeState.requestDataChannel = null; exchangeState.responseCapacityChannel = null; clientEndpoint.releaseAndDiscard(); + } finally { + lock.unlock(); } } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicClientSNIExample.java b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicClientSNIExample.java new file mode 100644 index 0000000000..f263b85db1 --- /dev/null +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicClientSNIExample.java @@ -0,0 +1,110 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * 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("=============="); + } + } + +} diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicFileServerExample.java b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicFileServerExample.java index b8fbfd7a80..63e6ecd677 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicFileServerExample.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicFileServerExample.java @@ -144,8 +144,9 @@ public HttpFileHandler(final String docRoot) { public void handle( final ClassicHttpRequest request, final ClassicHttpResponse response, - final HttpContext context) throws HttpException, IOException { + 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"); @@ -180,8 +181,7 @@ public void handle( System.out.println(msg); } else { - final HttpCoreContext coreContext = HttpCoreContext.adapt(context); - final EndpointDetails endpoint = coreContext.getEndpointDetails(); + 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); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicGetExecutionExample.java b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicGetExecutionExample.java index 1b12d99798..ab7de44b14 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicGetExecutionExample.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicGetExecutionExample.java @@ -81,7 +81,7 @@ public void onExchangeComplete(final HttpConnection connection, final boolean ke .build()) .create(); - final HttpCoreContext coreContext = HttpCoreContext.create(); + final HttpCoreContext context = HttpCoreContext.create(); final HttpHost target = new HttpHost("httpbin.org"); final String[] requestUris = new String[] {"/", "/ip", "/user-agent", "/headers"}; @@ -91,7 +91,7 @@ public void onExchangeComplete(final HttpConnection connection, final boolean ke .setHttpHost(target) .setPath(requestUri) .build(); - try (ClassicHttpResponse response = httpRequester.execute(target, request, Timeout.ofSeconds(5), coreContext)) { + 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("=============="); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicPostExecutionExample.java b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicPostExecutionExample.java index e574def453..49644d1042 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicPostExecutionExample.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/examples/ClassicPostExecutionExample.java @@ -85,7 +85,7 @@ public void onExchangeComplete(final HttpConnection connection, final boolean ke .setSoTimeout(5, TimeUnit.SECONDS) .build()) .create(); - final HttpCoreContext coreContext = HttpCoreContext.create(); + final HttpCoreContext context = HttpCoreContext.create(); final HttpHost target = new HttpHost("httpbin.org"); final HttpEntity[] requestBodies = { @@ -106,12 +106,12 @@ public void onExchangeComplete(final HttpConnection connection, final boolean ke final String requestUri = "/post"; for (int i = 0; i < requestBodies.length; i++) { - final ClassicHttpRequest request = ClassicRequestBuilder.post() + final ClassicHttpRequest request = ClassicRequestBuilder.get() .setHttpHost(target) .setPath(requestUri) .build(); request.setEntity(requestBodies[i]); - try (ClassicHttpResponse response = httpRequester.execute(target, request, Timeout.ofSeconds(5), coreContext)) { + 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("=============="); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/IncomingEntityDetailsTest.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/IncomingEntityDetailsTest.java index 71204ae228..87e759c928 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/IncomingEntityDetailsTest.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/IncomingEntityDetailsTest.java @@ -46,28 +46,28 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class IncomingEntityDetailsTest { +class IncomingEntityDetailsTest { @Test - public void getContentLengthEmpty() { + 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(incomingEntityDetails.getTrailerNames().size(), 0) + () -> assertEquals(0, incomingEntityDetails.getTrailerNames().size()) ); } @Test - public void messageHeadersNull() { + void messageHeadersNull() { Assertions.assertThrows(NullPointerException.class, () -> new IncomingEntityDetails(null), "Message Header Null"); } @Test - public void getContentLength() { + void getContentLength() { final MessageHeaders messageHeaders = new HeaderGroup(); final HeaderGroup headerGroup = new HeaderGroup(); final Header header = new BasicHeader("name", "value"); @@ -80,7 +80,7 @@ public void getContentLength() { } @Test - public void getTrailerNames() { + void getTrailerNames() { final HeaderGroup messageHeaders = new HeaderGroup(); final Header header = new BasicHeader(HttpHeaders.TRAILER, "a, b, c, c"); messageHeaders.setHeaders(header); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/TestDefaultConnectionReuseStrategy.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/TestDefaultConnectionReuseStrategy.java index d8af1a276a..b9056f072a 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/TestDefaultConnectionReuseStrategy.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/TestDefaultConnectionReuseStrategy.java @@ -35,54 +35,53 @@ 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.BasicHttpContext; -import org.apache.hc.core5.http.protocol.HttpContext; +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; -public class TestDefaultConnectionReuseStrategy { +class TestDefaultConnectionReuseStrategy { /** HTTP context. */ - private HttpContext context; + private HttpCoreContext context; /** The reuse strategy to be tested. */ private ConnectionReuseStrategy reuseStrategy; @BeforeEach - public void setUp() { + void setUp() { reuseStrategy = DefaultConnectionReuseStrategy.INSTANCE; - context = new BasicHttpContext(null); + context = HttpCoreContext.create(); } @Test - public void testInvalidResponseArg() throws Exception { + void testInvalidResponseArg() { Assertions.assertThrows(NullPointerException.class, () -> reuseStrategy.keepAlive(null, null, this.context)); } @Test - public void testNoContentLengthResponseHttp1_0() throws Exception { + void testNoContentLengthResponseHttp1_0() { context.setProtocolVersion(HttpVersion.HTTP_1_0); final HttpResponse response = new BasicHttpResponse(200, "OK"); Assertions.assertFalse(reuseStrategy.keepAlive(null, response, context)); } @Test - public void testNoContentLengthResponseHttp1_1() throws Exception { + void testNoContentLengthResponseHttp1_1() { final HttpResponse response = new BasicHttpResponse(200, "OK"); Assertions.assertFalse(reuseStrategy.keepAlive(null, response, context)); } @Test - public void testChunkedContent() throws Exception { + void testChunkedContent() { final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Transfer-Encoding", "chunked"); Assertions.assertTrue(reuseStrategy.keepAlive(null, response, context)); } @Test - public void testIgnoreInvalidKeepAlive() throws Exception { + void testIgnoreInvalidKeepAlive() { context.setProtocolVersion(HttpVersion.HTTP_1_0); final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Connection", "keep-alive"); @@ -90,7 +89,7 @@ public void testIgnoreInvalidKeepAlive() throws Exception { } @Test - public void testExplicitClose() throws Exception { + void testExplicitClose() { // Use HTTP 1.1 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Transfer-Encoding", "chunked"); @@ -99,7 +98,7 @@ public void testExplicitClose() throws Exception { } @Test - public void testExplicitKeepAlive() throws Exception { + void testExplicitKeepAlive() { // Use HTTP 1.0 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.setVersion(HttpVersion.HTTP_1_0); @@ -110,7 +109,7 @@ public void testExplicitKeepAlive() throws Exception { } @Test - public void testHTTP10Default() throws Exception { + void testHTTP10Default() { final HttpResponse response = new BasicHttpResponse(200, "OK"); response.setVersion(HttpVersion.HTTP_1_0); response.addHeader("Content-Length", "10"); @@ -118,14 +117,14 @@ public void testHTTP10Default() throws Exception { } @Test - public void testHTTP11Default() throws Exception { + void testHTTP11Default() { final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Content-Length", "10"); Assertions.assertTrue(reuseStrategy.keepAlive(null, response, context)); } @Test - public void testBrokenConnectionDirective1() throws Exception { + void testBrokenConnectionDirective1() { // Use HTTP 1.0 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.setVersion(HttpVersion.HTTP_1_0); @@ -135,7 +134,7 @@ public void testBrokenConnectionDirective1() throws Exception { } @Test - public void testBrokenConnectionDirective2() throws Exception { + void testBrokenConnectionDirective2() { // Use HTTP 1.0 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.setVersion(HttpVersion.HTTP_1_0); @@ -145,7 +144,7 @@ public void testBrokenConnectionDirective2() throws Exception { } @Test - public void testConnectionTokens1() throws Exception { + void testConnectionTokens1() { // Use HTTP 1.1 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Transfer-Encoding", "chunked"); @@ -154,7 +153,7 @@ public void testConnectionTokens1() throws Exception { } @Test - public void testConnectionTokens2() throws Exception { + void testConnectionTokens2() { // Use HTTP 1.1 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Transfer-Encoding", "chunked"); @@ -163,7 +162,7 @@ public void testConnectionTokens2() throws Exception { } @Test - public void testConnectionTokens3() throws Exception { + void testConnectionTokens3() { // Use HTTP 1.1 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Transfer-Encoding", "chunked"); @@ -172,7 +171,7 @@ public void testConnectionTokens3() throws Exception { } @Test - public void testConnectionTokens4() throws Exception { + void testConnectionTokens4() { // Use HTTP 1.1 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Transfer-Encoding", "chunked"); @@ -183,7 +182,7 @@ public void testConnectionTokens4() throws Exception { } @Test - public void testConnectionTokens5() throws Exception { + void testConnectionTokens5() { // Use HTTP 1.1 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Transfer-Encoding", "chunked"); @@ -196,7 +195,7 @@ public void testConnectionTokens5() throws Exception { } @Test - public void testConnectionTokens6() throws Exception { + void testConnectionTokens6() { // Use HTTP 1.1 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Transfer-Encoding", "chunked"); @@ -208,7 +207,7 @@ public void testConnectionTokens6() throws Exception { } @Test - public void testMultipleContentLength() throws Exception { + void testMultipleContentLength() { // Use HTTP 1.1 final HttpResponse response = new BasicHttpResponse(200, "OK"); response.addHeader("Content-Length", "10"); @@ -217,14 +216,14 @@ public void testMultipleContentLength() throws Exception { } @Test - public void testNoContentResponse() throws Exception { + 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 - public void testNoContentResponseHttp10() throws Exception { + void testNoContentResponseHttp10() { // Use HTTP 1.0 final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content"); response.setVersion(HttpVersion.HTTP_1_0); @@ -232,7 +231,7 @@ public void testNoContentResponseHttp10() throws Exception { } @Test - public void testRequestExplicitClose() throws Exception { + void testRequestExplicitClose() { final HttpRequest request = new BasicHttpRequest(Method.GET, "/"); request.addHeader("Connection", "close"); @@ -243,7 +242,7 @@ public void testRequestExplicitClose() throws Exception { } @Test - public void testRequestNoExplicitClose() throws Exception { + void testRequestNoExplicitClose() { final HttpRequest request = new BasicHttpRequest(Method.GET, "/"); request.addHeader("Connection", "blah, blah, blah"); @@ -254,7 +253,7 @@ public void testRequestNoExplicitClose() throws Exception { } @Test - public void testRequestExplicitCloseMultipleTokens() throws Exception { + void testRequestExplicitCloseMultipleTokens() { final HttpRequest request = new BasicHttpRequest(Method.GET, "/"); request.addHeader("Connection", "blah, blah, blah"); request.addHeader("Connection", "keep-alive"); @@ -267,7 +266,7 @@ public void testRequestExplicitCloseMultipleTokens() throws Exception { } @Test - public void testRequestClose() throws Exception { + void testRequestClose() { final HttpRequest request = new BasicHttpRequest(Method.GET, "/"); request.addHeader("Connection", "close"); @@ -279,7 +278,7 @@ public void testRequestClose() throws Exception { } @Test - public void testHeadRequestWithout() throws Exception { + void testHeadRequestWithout() { final HttpRequest request = new BasicHttpRequest(Method.HEAD, "/"); final HttpResponse response = new BasicHttpResponse(200, "OK"); @@ -287,7 +286,7 @@ public void testHeadRequestWithout() throws Exception { } @Test - public void testHttp204ContentLengthGreaterThanZero() throws Exception { + void testHttp204ContentLengthGreaterThanZero() { final HttpResponse response = new BasicHttpResponse(204, "OK"); response.addHeader("Content-Length", "10"); response.addHeader("Connection", "keep-alive"); @@ -295,7 +294,7 @@ public void testHttp204ContentLengthGreaterThanZero() throws Exception { } @Test - public void testHttp204ContentLengthEqualToZero() throws Exception { + void testHttp204ContentLengthEqualToZero() { final HttpResponse response = new BasicHttpResponse(204, "OK"); response.addHeader("Content-Length", "0"); response.addHeader("Connection", "keep-alive"); @@ -303,11 +302,21 @@ public void testHttp204ContentLengthEqualToZero() throws Exception { } @Test - public void testHttp204ChunkedContent() throws Exception { + 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)); + } + } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/TestDefaultContentLengthStrategy.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/TestDefaultContentLengthStrategy.java index 8f6df3584b..03551b8fed 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/TestDefaultContentLengthStrategy.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/TestDefaultContentLengthStrategy.java @@ -37,7 +37,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestDefaultContentLengthStrategy { +class TestDefaultContentLengthStrategy { static class TestHttpMessage extends HeaderGroup implements HttpMessage { @@ -65,7 +65,7 @@ public void setVersion(final ProtocolVersion version) { } @Test - public void testEntityWithChunkTransferEncoding() throws Exception { + void testEntityWithChunkTransferEncoding() throws Exception { final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy(); final HttpMessage message = new TestHttpMessage(); message.addHeader("Transfer-Encoding", "Chunked"); @@ -74,25 +74,61 @@ public void testEntityWithChunkTransferEncoding() throws Exception { } @Test - public void testEntityWithIdentityTransferEncoding() throws Exception { + void testEntityWithChunkTransferEncodingAndEmptyTokens() throws Exception { final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy(); final HttpMessage message = new TestHttpMessage(); - message.addHeader("Transfer-Encoding", "Identity"); + 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 - public void testEntityWithInvalidTransferEncoding() throws Exception { + void testEntityWithChunkTransferEncodingUnknown1() { final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy(); final HttpMessage message = new TestHttpMessage(); - message.addHeader("Transfer-Encoding", "whatever"); - Assertions.assertThrows(ProtocolException.class, () -> + 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 - public void testEntityWithContentLength() throws Exception { + void testEntityWithContentLength() throws Exception { final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy(); final HttpMessage message = new TestHttpMessage(); message.addHeader("Content-Length", "100"); @@ -100,7 +136,7 @@ public void testEntityWithContentLength() throws Exception { } @Test - public void testEntityWithInvalidContentLength() throws Exception { + void testEntityWithInvalidContentLength() { final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy(); final HttpMessage message = new TestHttpMessage(); message.addHeader("Content-Length", "whatever"); @@ -109,7 +145,7 @@ public void testEntityWithInvalidContentLength() throws Exception { } @Test - public void testEntityWithNegativeContentLength() throws Exception { + void testEntityWithNegativeContentLength() { final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy(); final HttpMessage message = new TestHttpMessage(); message.addHeader("Content-Length", "-10"); @@ -118,7 +154,7 @@ public void testEntityWithNegativeContentLength() throws Exception { } @Test - public void testEntityNoContentDelimiter() throws Exception { + void testEntityNoContentDelimiter() throws Exception { final ContentLengthStrategy lenStrategy = new DefaultContentLengthStrategy(); final HttpMessage message = new TestHttpMessage(); Assertions.assertEquals(ContentLengthStrategy.UNDEFINED, lenStrategy.determineLength(message)); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/TestEnglishReasonPhraseCatalog.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/TestEnglishReasonPhraseCatalog.java index ae89fb19ee..2777adb768 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/TestEnglishReasonPhraseCatalog.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/TestEnglishReasonPhraseCatalog.java @@ -39,10 +39,10 @@ * Unit tests for {@link EnglishReasonPhraseCatalog} * */ -public class TestEnglishReasonPhraseCatalog { +class TestEnglishReasonPhraseCatalog { @Test - public void testReasonPhrases() throws IllegalAccessException { + void testReasonPhrases() throws IllegalAccessException { final Field[] publicFields = HttpStatus.class.getFields(); Assertions.assertNotNull( publicFields ); @@ -68,7 +68,7 @@ public void testReasonPhrases() throws IllegalAccessException { @Test - public void testStatusInvalid() throws Exception { + void testStatusInvalid() { Assertions.assertThrows(IllegalArgumentException.class, () -> EnglishReasonPhraseCatalog.INSTANCE.getReason(-1, null)); Assertions.assertThrows(IllegalArgumentException.class, () -> @@ -78,7 +78,7 @@ public void testStatusInvalid() throws Exception { } @Test - public void testStatusAll() throws Exception { + void testStatusAll() { for (int i = 100; i < 600; i++) { EnglishReasonPhraseCatalog.INSTANCE.getReason(i, null); } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestBHttpConnectionBase.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestBHttpConnectionBase.java index 1fc7fc12ab..6379695878 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestBHttpConnectionBase.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestBHttpConnectionBase.java @@ -50,7 +50,7 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -public class TestBHttpConnectionBase { +class TestBHttpConnectionBase { @Mock private Socket socket; @@ -58,13 +58,13 @@ public class TestBHttpConnectionBase { private BHttpConnectionBase conn; @BeforeEach - public void prepareMocks() { + void prepareMocks() { MockitoAnnotations.openMocks(this); conn = new BHttpConnectionBase(Http1Config.DEFAULT, null, null); } @Test - public void testBasics() throws Exception { + void testBasics() { Assertions.assertFalse(conn.isOpen()); Assertions.assertNull(conn.getLocalAddress()); Assertions.assertNull(conn.getRemoteAddress()); @@ -72,7 +72,7 @@ public void testBasics() throws Exception { } @Test - public void testSocketBind() throws Exception { + 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}); @@ -93,7 +93,7 @@ public void testSocketBind() throws Exception { } @Test - public void testConnectionClose() throws Exception { + void testConnectionClose() throws Exception { final OutputStream outStream = Mockito.mock(OutputStream.class); Mockito.when(socket.getOutputStream()).thenReturn(outStream); @@ -118,7 +118,7 @@ public void testConnectionClose() throws Exception { } @Test - public void testConnectionShutdown() throws Exception { + void testConnectionShutdown() throws Exception { final OutputStream outStream = Mockito.mock(OutputStream.class); conn.bind(socket); @@ -145,7 +145,7 @@ public void testConnectionShutdown() throws Exception { } @Test - public void testCreateEntityLengthDelimited() throws Exception { + void testCreateEntityLengthDelimited() throws Exception { final InputStream inStream = Mockito.mock(InputStream.class); final ClassicHttpResponse message = new BasicClassicHttpResponse(200, "OK"); message.addHeader("Content-Length", "10"); @@ -163,7 +163,7 @@ public void testCreateEntityLengthDelimited() throws Exception { } @Test - public void testCreateEntityInputChunked() throws Exception { + 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); @@ -176,7 +176,7 @@ public void testCreateEntityInputChunked() throws Exception { } @Test - public void testCreateEntityInputUndefined() throws Exception { + 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); @@ -189,7 +189,7 @@ public void testCreateEntityInputUndefined() throws Exception { } @Test - public void testSetSocketTimeout() throws Exception { + void testSetSocketTimeout() throws Exception { conn.bind(socket); conn.setSocketTimeout(Timeout.ofMilliseconds(123)); @@ -198,7 +198,7 @@ public void testSetSocketTimeout() throws Exception { } @Test - public void testSetSocketTimeoutException() throws Exception { + void testSetSocketTimeoutException() throws Exception { conn.bind(socket); Mockito.doThrow(new SocketException()).when(socket).setSoTimeout(ArgumentMatchers.anyInt()); @@ -209,8 +209,8 @@ public void testSetSocketTimeoutException() throws Exception { } @Test - public void testGetSocketTimeout() throws Exception { - Assertions.assertEquals(Timeout.DISABLED, conn.getSocketTimeout()); + void testGetSocketTimeout() throws Exception { + Assertions.assertEquals(Timeout.INFINITE, conn.getSocketTimeout()); Mockito.when(socket.getSoTimeout()).thenReturn(345); conn.bind(socket); @@ -219,17 +219,17 @@ public void testGetSocketTimeout() throws Exception { } @Test - public void testGetSocketTimeoutException() throws Exception { - Assertions.assertEquals(Timeout.DISABLED, conn.getSocketTimeout()); + void testGetSocketTimeoutException() throws Exception { + Assertions.assertEquals(Timeout.INFINITE, conn.getSocketTimeout()); Mockito.when(socket.getSoTimeout()).thenThrow(new SocketException()); conn.bind(socket); - Assertions.assertEquals(Timeout.DISABLED, conn.getSocketTimeout()); + Assertions.assertEquals(Timeout.INFINITE, conn.getSocketTimeout()); } @Test - public void testAwaitInputInBuffer() throws Exception { + void testAwaitInputInBuffer() throws Exception { final ByteArrayInputStream inStream = new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5}); conn.bind(socket); @@ -242,7 +242,7 @@ public void testAwaitInputInBuffer() throws Exception { } @Test - public void testAwaitInputInSocket() throws Exception { + void testAwaitInputInSocket() throws Exception { final ByteArrayInputStream inStream = new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5}); Mockito.when(socket.getInputStream()).thenReturn(inStream); @@ -258,7 +258,7 @@ public void testAwaitInputInSocket() throws Exception { } @Test - public void testAwaitInputNoData() throws Exception { + 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())) @@ -271,7 +271,7 @@ public void testAwaitInputNoData() throws Exception { } @Test - public void testStaleWhenClosed() throws Exception { + void testStaleWhenClosed() throws Exception { final OutputStream outStream = Mockito.mock(OutputStream.class); Mockito.when(socket.getOutputStream()).thenReturn(outStream); @@ -283,7 +283,7 @@ public void testStaleWhenClosed() throws Exception { } @Test - public void testNotStaleWhenHasData() throws Exception { + void testNotStaleWhenHasData() throws Exception { final ByteArrayInputStream inStream = new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5}); Mockito.when(socket.getInputStream()).thenReturn(inStream); @@ -295,7 +295,7 @@ public void testNotStaleWhenHasData() throws Exception { } @Test - public void testStaleWhenEndOfStream() throws Exception { + 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())) @@ -308,7 +308,7 @@ public void testStaleWhenEndOfStream() throws Exception { } @Test - public void testNotStaleWhenTimeout() throws Exception { + 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())) @@ -321,7 +321,7 @@ public void testNotStaleWhenTimeout() throws Exception { } @Test - public void testStaleWhenIOError() throws Exception { + 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())) diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestChunkCoding.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestChunkCoding.java index 8678650279..7a4a976457 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestChunkCoding.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestChunkCoding.java @@ -45,7 +45,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestChunkCoding { +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"; @@ -54,9 +54,9 @@ public class TestChunkCoding { = "123456789012345612345"; @Test - public void testChunkedInputStreamLargeBuffer() throws IOException { + void testChunkedInputStreamLargeBuffer() throws IOException { final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); - final ByteArrayInputStream inputStream = new ByteArrayInputStream(CHUNKED_INPUT.getBytes(StandardCharsets.ISO_8859_1)); + 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(); @@ -69,8 +69,8 @@ public void testChunkedInputStreamLargeBuffer() throws IOException { in.close(); - final String result = new String(out.toByteArray(), StandardCharsets.ISO_8859_1); - Assertions.assertEquals(result, CHUNKED_RESULT); + final String result = new String(out.toByteArray(), StandardCharsets.US_ASCII); + Assertions.assertEquals(CHUNKED_RESULT, result); final Header[] footers = in.getFooters(); Assertions.assertNotNull(footers); @@ -83,9 +83,9 @@ public void testChunkedInputStreamLargeBuffer() throws IOException { //Test for when buffer is smaller than chunk size. @Test - public void testChunkedInputStreamSmallBuffer() throws IOException { + void testChunkedInputStreamSmallBuffer() throws IOException { final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); - final ByteArrayInputStream inputStream = new ByteArrayInputStream(CHUNKED_INPUT.getBytes(StandardCharsets.ISO_8859_1)); + final ByteArrayInputStream inputStream = new ByteArrayInputStream(CHUNKED_INPUT.getBytes(StandardCharsets.US_ASCII)); final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream); final byte[] buffer = new byte[7]; @@ -110,40 +110,39 @@ public void testChunkedInputStreamSmallBuffer() throws IOException { // One byte read @Test - public void testChunkedInputStreamOneByteRead() throws IOException { + 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.ISO_8859_1)); - final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream); - int ch; - int i = '0'; - while ((ch = in.read()) != -1) { - Assertions.assertEquals(i, ch); - i++; + 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()); } - Assertions.assertEquals(-1, in.read()); - Assertions.assertEquals(-1, in.read()); - - in.close(); } @Test - public void testAvailable() throws IOException { + 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.ISO_8859_1)); - final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream); - Assertions.assertEquals(0, in.available()); - in.read(); - Assertions.assertEquals(4, in.available()); - in.close(); + 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 - public void testChunkedInputStreamClose() throws IOException { + 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.ISO_8859_1)); + final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream); in.close(); in.close(); @@ -155,150 +154,152 @@ public void testChunkedInputStreamClose() throws IOException { // Missing closing chunk @Test - public void testChunkedInputStreamNoClosingChunk() throws IOException { + 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.ISO_8859_1)); - final 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()); + 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 - public void testCorruptChunkedInputStreamTruncatedCRLF() throws IOException { + void testCorruptChunkedInputStreamTruncatedCRLF() throws IOException { final String s = "5\r\n01234"; final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); - final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.ISO_8859_1)); - final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream); - final byte[] tmp = new byte[5]; - Assertions.assertEquals(5, in.read(tmp)); - Assertions.assertThrows(MalformedChunkCodingException.class, () -> in.read()); - in.close(); + 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 - public void testCorruptChunkedInputStreamMissingCRLF() throws IOException { + 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.ISO_8859_1)); - final 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); - } - }); - in.close(); + 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 - public void testCorruptChunkedInputStreamMissingLF() throws IOException { + 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.ISO_8859_1)); - final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream); - Assertions.assertThrows(MalformedChunkCodingException.class, in::read); - in.close(); + 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 - public void testCorruptChunkedInputStreamInvalidSize() throws IOException { + 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.ISO_8859_1)); - final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream); - Assertions.assertThrows(MalformedChunkCodingException.class, in::read); - in.close(); + 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 - public void testCorruptChunkedInputStreamNegativeSize() throws IOException { + 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.ISO_8859_1)); - final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream); - Assertions.assertThrows(MalformedChunkCodingException.class, in::read); - in.close(); + 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 - public void testCorruptChunkedInputStreamTruncatedChunk() throws IOException { + void testCorruptChunkedInputStreamTruncatedChunk() throws IOException { final String s = "3\r\n12"; final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); - final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.ISO_8859_1)); - final 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)); - in.close(); + 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 - public void testCorruptChunkedInputStreamInvalidFooter() throws IOException { + 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.ISO_8859_1)); - final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream); - in.read(); - Assertions.assertThrows(MalformedChunkCodingException.class, in::read); - in.close(); + 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 - public void testCorruptChunkedInputStreamClose() throws IOException { + 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.ISO_8859_1)); + 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 - public void testEmptyChunkedInputStream() throws IOException { + void testEmptyChunkedInputStream() throws IOException { final String s = "0\r\n"; final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); - final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.ISO_8859_1)); - 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); + 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()); } - Assertions.assertEquals(0, out.size()); - in.close(); } @Test - public void testTooLongChunkHeader() throws IOException { + 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.ISO_8859_1)); + 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.ISO_8859_1)); + 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 - public void testChunkedOutputStreamClose() throws IOException { + void testChunkedOutputStreamClose() throws IOException { final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2048); @@ -309,152 +310,152 @@ public void testChunkedOutputStreamClose() throws IOException { } @Test - public void testChunkedConsistence() throws IOException { + 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(); - final ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2048); - out.write(input.getBytes(StandardCharsets.ISO_8859_1)); - out.flush(); - out.close(); - out.close(); + 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()); - final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream); + 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 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.ISO_8859_1); - Assertions.assertEquals(input, output); - in.close(); + final String output = new String(result.toByteArray(), StandardCharsets.US_ASCII); + Assertions.assertEquals(input, output); + } } @Test - public void testChunkedOutputStreamWithTrailers() throws IOException { + void testChunkedOutputStreamWithTrailers() throws IOException { final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - final ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2, () -> Arrays.asList( + try (ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2, () -> Arrays.asList( new BasicHeader("E", ""), new BasicHeader("Y", "Z")) - ); - out.write('x'); - out.finish(); - out.close(); + )) { + 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 - public void testChunkedOutputStream() throws IOException { + void testChunkedOutputStream() throws IOException { final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - final ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2); - out.write('1'); - out.write('2'); - out.write('3'); - out.write('4'); - out.finish(); - out.close(); + 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 - public void testChunkedOutputStreamLargeChunk() throws IOException { + void testChunkedOutputStreamLargeChunk() throws IOException { final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - final ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2); - out.write(new byte[] {'1', '2', '3', '4'}); - out.finish(); - out.close(); + 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 - public void testChunkedOutputStreamSmallChunk() throws IOException { + void testChunkedOutputStreamSmallChunk() throws IOException { final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - final ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2); - out.write('1'); - out.finish(); - out.close(); + 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 - public void testResumeOnSocketTimeoutInData() throws IOException { + 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); - final TimeoutByteArrayInputStream inputStream = new TimeoutByteArrayInputStream(s.getBytes(StandardCharsets.ISO_8859_1)); - final 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; + 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++; } - } catch (final InterruptedIOException ex) { - timeouts++; } + Assertions.assertEquals(20, bytesRead); + Assertions.assertEquals(2, timeouts); } - Assertions.assertEquals(20, bytesRead); - Assertions.assertEquals(2, timeouts); - in.close(); -} + } @Test - public void testResumeOnSocketTimeoutInChunk() throws IOException { + 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); - final TimeoutByteArrayInputStream inputStream = new TimeoutByteArrayInputStream(s.getBytes(StandardCharsets.ISO_8859_1)); - final 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; + 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++; } - } catch (final InterruptedIOException ex) { - timeouts++; } + Assertions.assertEquals(20, bytesRead); + Assertions.assertEquals(5, timeouts); } - Assertions.assertEquals(20, bytesRead); - Assertions.assertEquals(5, timeouts); - in.close(); } // Test for when buffer is larger than chunk size @Test - public void testHugeChunk() throws IOException { + void testHugeChunk() throws IOException { final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); final ByteArrayInputStream inputStream = new ByteArrayInputStream("1234567890abcdef\r\n01234567".getBytes( - StandardCharsets.ISO_8859_1)); + StandardCharsets.US_ASCII)); final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream); final ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -462,7 +463,7 @@ public void testHugeChunk() throws IOException { out.write(in.read()); } - final String result = new String(out.toByteArray(), StandardCharsets.ISO_8859_1); + final String result = new String(out.toByteArray(), StandardCharsets.US_ASCII); Assertions.assertEquals("01234567", result); } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestContentLengthInputStream.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestContentLengthInputStream.java index 22e7134687..610c877a40 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestContentLengthInputStream.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestContentLengthInputStream.java @@ -39,77 +39,77 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestContentLengthInputStream { +class TestContentLengthInputStream { @Test - public void testBasics() throws IOException { + void testBasics() throws IOException { final String s = "1234567890123456"; - final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.ISO_8859_1)); + final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); - final 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.ISO_8859_1); - Assertions.assertEquals(result, "1234567890"); - in.close(); + 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 - public void testSkip() throws IOException { + void testSkip() throws IOException { final ByteArrayInputStream inputStream1 = new ByteArrayInputStream(new byte[20]); final SessionInputBuffer inBuffer1 = new SessionInputBufferImpl(16); - final InputStream in1 = new ContentLengthInputStream(inBuffer1, inputStream1, 10L); - Assertions.assertEquals(10, in1.skip(10)); - Assertions.assertEquals(-1, in1.read()); - in1.close(); + 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); - final InputStream in2 = new ContentLengthInputStream(inBuffer2, inputStream2, 10L); - in2.read(); - Assertions.assertEquals(9, in2.skip(10)); - Assertions.assertEquals(-1, in2.read()); - in2.close(); + 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); - final 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()); - in3.close(); + 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); - final InputStream in4 = new ContentLengthInputStream(inBuffer4, inputStream4, 10L); - Assertions.assertEquals(5,in4.skip(5)); - Assertions.assertEquals(5, in4.read(new byte[20])); - in4.close(); + try (InputStream in4 = new ContentLengthInputStream(inBuffer4, inputStream4, 10L)) { + Assertions.assertEquals(5, in4.skip(5)); + Assertions.assertEquals(5, in4.read(new byte[20])); + } } @Test - public void testAvailable() throws IOException { + void testAvailable() throws IOException { final ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[] {1, 2, 3}); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); - final InputStream in = new ContentLengthInputStream(inBuffer, inputStream, 3L); - Assertions.assertEquals(0, in.available()); - in.read(); - Assertions.assertEquals(2, in.available()); - in.close(); + try (InputStream in = new ContentLengthInputStream(inBuffer, inputStream, 3L)) { + Assertions.assertEquals(0, in.available()); + in.read(); + Assertions.assertEquals(2, in.available()); + } } @Test - public void testClose() throws IOException { + void testClose() throws IOException { final String s = "1234567890123456-"; - final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.ISO_8859_1)); + 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(); @@ -122,9 +122,9 @@ public void testClose() throws IOException { } @Test - public void testTruncatedContent() throws IOException { + void testTruncatedContent() throws IOException { final String s = "1234567890123456"; - final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.ISO_8859_1)); + 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]; diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestContentLengthOutputStream.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestContentLengthOutputStream.java index 4afd82e5c4..638dce2df8 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestContentLengthOutputStream.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestContentLengthOutputStream.java @@ -35,30 +35,30 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestContentLengthOutputStream { +class TestContentLengthOutputStream { @Test - public void testBasics() throws Exception { + void testBasics() throws Exception { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); - final OutputStream out = new ContentLengthOutputStream(outbuffer, outputStream, 15L); + 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(); - out.close(); + 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 - public void testClose() throws Exception { + void testClose() throws Exception { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final OutputStream out = new ContentLengthOutputStream(outbuffer, outputStream, 15L); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestDefaultBHttpClientConnection.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestDefaultBHttpClientConnection.java index 12e3a23823..256af2b7fd 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestDefaultBHttpClientConnection.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestDefaultBHttpClientConnection.java @@ -51,7 +51,7 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -public class TestDefaultBHttpClientConnection { +class TestDefaultBHttpClientConnection { @Mock private Socket socket; @@ -59,7 +59,7 @@ public class TestDefaultBHttpClientConnection { private DefaultBHttpClientConnection conn; @BeforeEach - public void prepareMocks() { + void prepareMocks() { MockitoAnnotations.openMocks(this); conn = new DefaultBHttpClientConnection(Http1Config.DEFAULT, null, null, @@ -70,13 +70,13 @@ public void prepareMocks() { } @Test - public void testBasics() throws Exception { + void testBasics() { Assertions.assertFalse(conn.isOpen()); Assertions.assertEquals("[Not bound]", conn.toString()); } @Test - public void testReadResponseHead() throws Exception { + 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); @@ -93,7 +93,7 @@ public void testReadResponseHead() throws Exception { } @Test - public void testReadResponseEntityWithoutContentLength() throws Exception { + 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); @@ -125,7 +125,7 @@ public void testReadResponseEntityWithoutContentLength() throws Exception { } @Test - public void testReadResponseEntityWithContentLength() throws Exception { + 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); @@ -153,7 +153,7 @@ public void testReadResponseEntityWithContentLength() throws Exception { } @Test - public void testReadResponseEntityChunkCoded() throws Exception { + 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)); @@ -183,7 +183,7 @@ public void testReadResponseEntityChunkCoded() throws Exception { } @Test - public void testReadResponseEntityIdentity() throws Exception { + 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); @@ -204,7 +204,7 @@ public void testReadResponseEntityIdentity() throws Exception { } @Test - public void testReadResponseNoEntity() throws Exception { + 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); @@ -230,7 +230,7 @@ public void testReadResponseNoEntity() throws Exception { } @Test - public void testWriteRequestHead() throws Exception { + void testWriteRequestHead() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); @@ -250,7 +250,7 @@ public void testWriteRequestHead() throws Exception { } @Test - public void testWriteRequestEntityWithContentLength() throws Exception { + void testWriteRequestEntityWithContentLength() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); @@ -273,7 +273,7 @@ public void testWriteRequestEntityWithContentLength() throws Exception { } @Test - public void testWriteRequestEntityChunkCoded() throws Exception { + void testWriteRequestEntityChunkCoded() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); @@ -297,7 +297,7 @@ public void testWriteRequestEntityChunkCoded() throws Exception { } @Test - public void testWriteRequestEntityNoContentLength() throws Exception { + void testWriteRequestEntityNoContentLength() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); @@ -315,7 +315,7 @@ public void testWriteRequestEntityNoContentLength() throws Exception { } @Test - public void testWriteRequestNoEntity() throws Exception { + void testWriteRequestNoEntity() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); @@ -336,7 +336,7 @@ public void testWriteRequestNoEntity() throws Exception { } @Test - public void testTerminateRequestChunkedEntity() throws Exception { + void testTerminateRequestChunkedEntity() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); @@ -363,7 +363,7 @@ public void testTerminateRequestChunkedEntity() throws Exception { } @Test - public void testTerminateRequestContentLengthShort() throws Exception { + void testTerminateRequestContentLengthShort() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); @@ -389,7 +389,7 @@ public void testTerminateRequestContentLengthShort() throws Exception { } @Test - public void testTerminateRequestContentLengthLong() throws Exception { + void testTerminateRequestContentLengthLong() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestDefaultBHttpServerConnection.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestDefaultBHttpServerConnection.java index 78fb072ef0..c44c8ffce9 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestDefaultBHttpServerConnection.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestDefaultBHttpServerConnection.java @@ -51,7 +51,7 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -public class TestDefaultBHttpServerConnection { +class TestDefaultBHttpServerConnection { @Mock private Socket socket; @@ -59,7 +59,7 @@ public class TestDefaultBHttpServerConnection { private DefaultBHttpServerConnection conn; @BeforeEach - public void prepareMocks() { + void prepareMocks() { MockitoAnnotations.openMocks(this); conn = new DefaultBHttpServerConnection("http", Http1Config.DEFAULT, null, null, @@ -70,13 +70,13 @@ public void prepareMocks() { } @Test - public void testBasics() throws Exception { + void testBasics() { Assertions.assertFalse(conn.isOpen()); Assertions.assertEquals("[Not bound]", conn.toString()); } @Test - public void testReadRequestHead() throws Exception { + 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); @@ -99,7 +99,7 @@ public void testReadRequestHead() throws Exception { } @Test - public void testReadRequestEntityWithContentLength() throws Exception { + 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); @@ -129,7 +129,7 @@ public void testReadRequestEntityWithContentLength() throws Exception { } @Test - public void testReadRequestEntityChunckCoded() throws Exception { + 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)); @@ -161,7 +161,7 @@ public void testReadRequestEntityChunckCoded() throws Exception { } @Test - public void testReadRequestEntityIdentity() throws Exception { + 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)); @@ -185,7 +185,7 @@ public void testReadRequestEntityIdentity() throws Exception { } @Test - public void testReadRequestNoEntity() throws Exception { + 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); @@ -210,7 +210,7 @@ public void testReadRequestNoEntity() throws Exception { } @Test - public void testWriteResponseHead() throws Exception { + void testWriteResponseHead() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); @@ -230,7 +230,7 @@ public void testWriteResponseHead() throws Exception { } @Test - public void testWriteResponse100Head() throws Exception { + void testWriteResponse100Head() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); @@ -249,7 +249,7 @@ public void testWriteResponse100Head() throws Exception { } @Test - public void testWriteResponseEntityWithContentLength() throws Exception { + void testWriteResponseEntityWithContentLength() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); @@ -272,7 +272,7 @@ public void testWriteResponseEntityWithContentLength() throws Exception { } @Test - public void testWriteResponseEntityChunkCoded() throws Exception { + void testWriteResponseEntityChunkCoded() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); @@ -296,7 +296,7 @@ public void testWriteResponseEntityChunkCoded() throws Exception { } @Test - public void testWriteResponseEntityIdentity() throws Exception { + void testWriteResponseEntityIdentity() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); @@ -316,7 +316,7 @@ public void testWriteResponseEntityIdentity() throws Exception { } @Test - public void testWriteResponseNoEntity() throws Exception { + void testWriteResponseNoEntity() throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); Mockito.when(socket.getOutputStream()).thenReturn(outStream); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestHttpRequestExecutor.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestHttpRequestExecutor.java index 9535604b61..d961c5a283 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestHttpRequestExecutor.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestHttpRequestExecutor.java @@ -50,10 +50,10 @@ import org.mockito.ArgumentMatchers; import org.mockito.Mockito; -public class TestHttpRequestExecutor { +class TestHttpRequestExecutor { @Test - public void testInvalidInput() throws Exception { + void testInvalidInput() { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); @@ -99,7 +99,7 @@ public void testInvalidInput() throws Exception { } @Test - public void testBasicExecution() throws Exception { + void testBasicExecution() throws Exception { final HttpProcessor httprocessor = Mockito.mock(HttpProcessor.class); final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); @@ -127,7 +127,7 @@ public void testBasicExecution() throws Exception { } @Test - public void testExecutionSkipIntermediateResponses() throws Exception { + void testExecutionSkipIntermediateResponses() throws Exception { final HttpProcessor httprocessor = Mockito.mock(HttpProcessor.class); final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); @@ -172,7 +172,7 @@ public void testExecutionSkipIntermediateResponses() throws Exception { } @Test - public void testExecutionNoResponseBody() throws Exception { + void testExecutionNoResponseBody() throws Exception { final HttpProcessor httprocessor = Mockito.mock(HttpProcessor.class); final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); @@ -197,7 +197,7 @@ public void testExecutionNoResponseBody() throws Exception { } @Test - public void testExecutionHead() throws Exception { + void testExecutionHead() throws Exception { final HttpProcessor httprocessor = Mockito.mock(HttpProcessor.class); final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); @@ -222,7 +222,7 @@ public void testExecutionHead() throws Exception { } @Test - public void testExecutionEntityEnclosingRequest() throws Exception { + void testExecutionEntityEnclosingRequest() throws Exception { final HttpProcessor httprocessor = Mockito.mock(HttpProcessor.class); final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); @@ -250,7 +250,7 @@ public void testExecutionEntityEnclosingRequest() throws Exception { } @Test - public void testExecutionEntityEnclosingRequestWithExpectContinueSuccess() throws Exception { + void testExecutionEntityEnclosingRequestWithExpectContinueSuccess() throws Exception { final HttpProcessor httprocessor = Mockito.mock(HttpProcessor.class); final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); @@ -282,7 +282,7 @@ public void testExecutionEntityEnclosingRequestWithExpectContinueSuccess() throw } @Test - public void testExecutionEntityEnclosingRequestWithExpectContinueFailure() throws Exception { + void testExecutionEntityEnclosingRequestWithExpectContinueFailure() throws Exception { final HttpProcessor httprocessor = Mockito.mock(HttpProcessor.class); final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); @@ -314,7 +314,7 @@ public void testExecutionEntityEnclosingRequestWithExpectContinueFailure() throw } @Test - public void testExecutionEntityEnclosingRequestWithExpectContinueMultiple1xxResponses() throws Exception { + void testExecutionEntityEnclosingRequestWithExpectContinueMultiple1xxResponses() throws Exception { final HttpProcessor httprocessor = Mockito.mock(HttpProcessor.class); final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); @@ -362,7 +362,7 @@ public void testExecutionEntityEnclosingRequestWithExpectContinueMultiple1xxResp } @Test - public void testExecutionEntityEnclosingRequestWithExpectContinueNoResponse() throws Exception { + void testExecutionEntityEnclosingRequestWithExpectContinueNoResponse() throws Exception { final HttpProcessor httprocessor = Mockito.mock(HttpProcessor.class); final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); @@ -393,7 +393,7 @@ public void testExecutionEntityEnclosingRequestWithExpectContinueNoResponse() th } @Test - public void testExecutionIOException() throws Exception { + void testExecutionIOException() throws Exception { final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); @@ -406,7 +406,7 @@ public void testExecutionIOException() throws Exception { } @Test - public void testExecutionRuntimeException() throws Exception { + void testExecutionRuntimeException() throws Exception { final HttpClientConnection conn = Mockito.mock(HttpClientConnection.class); final HttpRequestExecutor executor = new HttpRequestExecutor(); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestHttpService.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestHttpService.java index 49858eab8f..ef073bb015 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestHttpService.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestHttpService.java @@ -59,7 +59,7 @@ import org.mockito.MockitoAnnotations; import org.mockito.Spy; -public class TestHttpService { +class TestHttpService { @Mock private HttpProcessor httprocessor; @@ -79,7 +79,7 @@ public class TestHttpService { private HttpService httpservice; @BeforeEach - public void prepareMocks() { + void prepareMocks() { MockitoAnnotations.openMocks(this); httpservice = new HttpService( httprocessor, @@ -89,7 +89,7 @@ public void prepareMocks() { } @Test - public void testInvalidInitialization() throws Exception { + void testInvalidInitialization() { Assertions.assertThrows(NullPointerException.class, () -> new HttpService( null, @@ -99,7 +99,7 @@ public void testInvalidInitialization() throws Exception { } @Test - public void testBasicExecution() throws Exception { + void testBasicExecution() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); Mockito.when(conn.receiveRequestHeader()).thenReturn(request); @@ -123,7 +123,7 @@ public void testBasicExecution() throws Exception { } @Test - public void testExecutionEntityEnclosingRequest() throws Exception { + void testExecutionEntityEnclosingRequest() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); final InputStream inStream = Mockito.mock(InputStream.class); @@ -155,7 +155,7 @@ public void testExecutionEntityEnclosingRequest() throws Exception { } @Test - public void testExecutionEntityEnclosingRequestWithExpectContinue() throws Exception { + void testExecutionEntityEnclosingRequestWithExpectContinue() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.addHeader(HttpHeaders.EXPECT, HeaderElements.CONTINUE); @@ -192,7 +192,7 @@ public void testExecutionEntityEnclosingRequestWithExpectContinue() throws Excep } @Test - public void testMethodNotSupported() throws Exception { + void testMethodNotSupported() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest("whatever", "/"); @@ -219,7 +219,7 @@ public void testMethodNotSupported() throws Exception { } @Test - public void testUnsupportedHttpVersionException() throws Exception { + void testUnsupportedHttpVersionException() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest("whatever", "/"); @@ -246,7 +246,7 @@ public void testUnsupportedHttpVersionException() throws Exception { } @Test - public void testProtocolException() throws Exception { + void testProtocolException() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest("whatever", "/"); @@ -273,7 +273,7 @@ public void testProtocolException() throws Exception { } @Test - public void testConnectionKeepAlive() throws Exception { + void testConnectionKeepAlive() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); Mockito.when(conn.receiveRequestHeader()).thenReturn(request); @@ -298,7 +298,7 @@ public void testConnectionKeepAlive() throws Exception { } @Test - public void testNoContentResponse() throws Exception { + void testNoContentResponse() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); @@ -321,7 +321,7 @@ public void testNoContentResponse() throws Exception { } @Test - public void testResponseToHead() throws Exception { + void testResponseToHead() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.HEAD, "/"); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestIdentityInputStream.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestIdentityInputStream.java index e822d46408..e6fc9522c6 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestIdentityInputStream.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestIdentityInputStream.java @@ -38,28 +38,28 @@ /** * Unit tests for {@link IdentityInputStream}. */ -public class TestIdentityInputStream { +class TestIdentityInputStream { @Test - public void testBasicRead() throws Exception { + void testBasicRead() 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); - 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()); - in.close(); + 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 - public void testClosedCondition() throws Exception { + void testClosedCondition() throws Exception { final byte[] input = new byte[] {'a', 'b', 'c'}; final ByteArrayInputStream inputStream = new ByteArrayInputStream(input); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); @@ -75,26 +75,26 @@ public void testClosedCondition() throws Exception { } @Test - public void testAvailable() throws Exception { + 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); - final IdentityInputStream in = new IdentityInputStream(inBuffer, inputStream); - in.read(); - Assertions.assertEquals(2, in.available()); - in.close(); + try (IdentityInputStream in = new IdentityInputStream(inBuffer, inputStream)) { + in.read(); + Assertions.assertEquals(2, in.available()); + } } @Test - public void testAvailableInStream() throws Exception { + 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); - final IdentityInputStream in = new IdentityInputStream(inBuffer, inputStream); - final byte[] tmp = new byte[3]; - Assertions.assertEquals(3, in.read(tmp)); - Assertions.assertEquals(3, in.available()); - in.close(); + try (IdentityInputStream in = new IdentityInputStream(inBuffer, inputStream)) { + final byte[] tmp = new byte[3]; + Assertions.assertEquals(3, in.read(tmp)); + Assertions.assertEquals(3, in.available()); + } } } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestIdentityOutputStream.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestIdentityOutputStream.java index 9c49c3760b..e513c96ac7 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestIdentityOutputStream.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestIdentityOutputStream.java @@ -39,26 +39,26 @@ /** * Unit tests for {@link IdentityOutputStream}. */ -public class TestIdentityOutputStream { +class TestIdentityOutputStream { @Test - public void testBasics() throws Exception { + void testBasics() throws Exception { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); - final OutputStream out = new IdentityOutputStream(outbuffer, outputStream); + 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(); - out.close(); + 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 - public void testClose() throws Exception { + void testClose() throws Exception { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final OutputStream out = new IdentityOutputStream(outbuffer, outputStream); @@ -70,27 +70,27 @@ public void testClose() throws Exception { } @Test - public void testBasicWrite() throws Exception { + void testBasicWrite() throws Exception { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); - final OutputStream out = new IdentityOutputStream(outbuffer, outputStream); - out.write(new byte[] {'a', 'b'}, 0, 2); - out.write('c'); - out.flush(); + 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(); + 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]); + 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]); + } } - out.close(); } @Test - public void testClosedCondition() throws Exception { + void testClosedCondition() throws Exception { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final OutputStream out = new IdentityOutputStream(outbuffer, outputStream); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestMessageParser.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestMessageParser.java index 151b5b8124..1b2d6a17db 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestMessageParser.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestMessageParser.java @@ -40,10 +40,10 @@ /** * Unit tests for {@link AbstractMessageParser}. */ -public class TestMessageParser { +class TestMessageParser { @Test - public void testBasicHeaderParsing() throws Exception { + void testBasicHeaderParsing() throws Exception { final String s = "header1: stuff\r\n" + "header2: stuff \r\n" + @@ -66,7 +66,7 @@ public void testBasicHeaderParsing() throws Exception { } @Test - public void testParsingHeader() throws Exception { + 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)); @@ -78,7 +78,7 @@ public void testParsingHeader() throws Exception { } @Test - public void testParsingInvalidHeaders() throws Exception { + void testParsingInvalidHeaders() { final String s1 = " stuff\r\n" + "header1: stuff\r\n" + "\r\n"; @@ -96,7 +96,7 @@ public void testParsingInvalidHeaders() throws Exception { } @Test - public void testParsingMalformedFirstHeader() throws Exception { + void testParsingMalformedFirstHeader() throws Exception { final String s = " header1: stuff\r\n" + "header2: stuff \r\n"; @@ -112,7 +112,7 @@ public void testParsingMalformedFirstHeader() throws Exception { } @Test - public void testEmptyDataStream() throws Exception { + 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()); @@ -122,7 +122,7 @@ public void testEmptyDataStream() throws Exception { } @Test - public void testMaxHeaderCount() throws Exception { + void testMaxHeaderCount() { final String s = "header1: stuff\r\n" + "header2: stuff \r\n" + @@ -135,7 +135,7 @@ public void testMaxHeaderCount() throws Exception { } @Test - public void testMaxHeaderCountForFoldedHeader() throws Exception { + void testMaxHeaderCountForFoldedHeader() { final String s = "header1: stuff\r\n" + " stuff \r\n" + diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestMonitoringResponseOutOfOrderStrategy.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestMonitoringResponseOutOfOrderStrategy.java index 389b74b62e..43e4937d16 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestMonitoringResponseOutOfOrderStrategy.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestMonitoringResponseOutOfOrderStrategy.java @@ -42,12 +42,12 @@ import org.mockito.ArgumentMatchers; import org.mockito.Mockito; -public class TestMonitoringResponseOutOfOrderStrategy { +class TestMonitoringResponseOutOfOrderStrategy { private static final ClassicHttpRequest REQUEST = new BasicClassicHttpRequest("POST", "/path"); @Test - public void testFirstByteIsNotCheckedSsl() throws IOException { + void testFirstByteIsNotCheckedSsl() throws IOException { final boolean earlyResponse = MonitoringResponseOutOfOrderStrategy.INSTANCE.isEarlyResponseDetected( REQUEST, connection(true, true), @@ -59,7 +59,7 @@ public void testFirstByteIsNotCheckedSsl() throws IOException { } @Test - public void testFirstByteIsNotCheckedPlain() throws IOException { + void testFirstByteIsNotCheckedPlain() throws IOException { final boolean earlyResponse = MonitoringResponseOutOfOrderStrategy.INSTANCE.isEarlyResponseDetected( REQUEST, connection(true, false), @@ -70,7 +70,7 @@ public void testFirstByteIsNotCheckedPlain() throws IOException { } @Test - public void testWritesWithinChunkAreNotChecked() throws IOException { + void testWritesWithinChunkAreNotChecked() throws IOException { final boolean earlyResponse = MonitoringResponseOutOfOrderStrategy.INSTANCE.isEarlyResponseDetected( REQUEST, connection(true, true), @@ -81,7 +81,7 @@ public void testWritesWithinChunkAreNotChecked() throws IOException { } @Test - public void testWritesAcrossChunksAreChecked() throws IOException { + void testWritesAcrossChunksAreChecked() throws IOException { final boolean earlyResponse = MonitoringResponseOutOfOrderStrategy.INSTANCE.isEarlyResponseDetected( REQUEST, connection(true, true), @@ -92,7 +92,7 @@ public void testWritesAcrossChunksAreChecked() throws IOException { } @Test - public void testMaximumChunks() throws IOException { + void testMaximumChunks() throws IOException { final ResponseOutOfOrderStrategy strategy = new MonitoringResponseOutOfOrderStrategy(1, 2); Assertions.assertTrue(strategy.isEarlyResponseDetected( REQUEST, diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestRequestParser.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestRequestParser.java index 729bea6556..bc65c754ce 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestRequestParser.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestRequestParser.java @@ -42,10 +42,10 @@ /** * Unit tests for {@link DefaultHttpRequestParser}. */ -public class TestRequestParser { +class TestRequestParser { @Test - public void testBasicMessageParsing() throws Exception { + void testBasicMessageParsing() throws Exception { final String s = "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + @@ -65,7 +65,7 @@ public void testBasicMessageParsing() throws Exception { } @Test - public void testConnectionClosedException() throws Exception { + void testConnectionClosedException() throws Exception { final ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[] {}); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); @@ -75,7 +75,7 @@ public void testConnectionClosedException() throws Exception { } @Test - public void testBasicMessageParsingLeadingEmptyLines() throws Exception { + void testBasicMessageParsingLeadingEmptyLines() throws Exception { final String s = "\r\n" + "\r\n" + @@ -96,7 +96,7 @@ public void testBasicMessageParsingLeadingEmptyLines() throws Exception { } @Test - public void testBasicMessageParsingTooManyLeadingEmptyLines() throws Exception { + void testBasicMessageParsingTooManyLeadingEmptyLines() { final String s = "\r\n" + "\r\n" + @@ -114,7 +114,7 @@ public void testBasicMessageParsingTooManyLeadingEmptyLines() throws Exception { } @Test - public void testMessageParsingTimeout() throws Exception { + void testMessageParsingTimeout() throws Exception { final String s = "GET \000/ HTTP/1.1\r\000\n" + "Host: loca\000lhost\r\n" + diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestResponseParser.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestResponseParser.java index 3918c15303..120739cfba 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestResponseParser.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestResponseParser.java @@ -42,10 +42,10 @@ /** * Unit tests for {@link DefaultHttpResponseParser}. */ -public class TestResponseParser { +class TestResponseParser { @Test - public void testBasicMessageParsing() throws Exception { + void testBasicMessageParsing() throws Exception { final String s = "HTTP/1.1 200 OK\r\n" + "Server: whatever\r\n" + @@ -65,7 +65,7 @@ public void testBasicMessageParsing() throws Exception { } @Test - public void testConnectionClosedException() throws Exception { + void testConnectionClosedException() throws Exception { final ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[] {}); final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16); @@ -75,7 +75,7 @@ public void testConnectionClosedException() throws Exception { } @Test - public void testBasicMessageParsingLeadingEmptyLines() throws Exception { + void testBasicMessageParsingLeadingEmptyLines() throws Exception { final String s = "\r\n" + "\r\n" + @@ -96,7 +96,7 @@ public void testBasicMessageParsingLeadingEmptyLines() throws Exception { } @Test - public void testBasicMessageParsingTooManyLeadingEmptyLines() throws Exception { + void testBasicMessageParsingTooManyLeadingEmptyLines() { final String s = "\r\n" + "\r\n" + @@ -114,7 +114,7 @@ public void testBasicMessageParsingTooManyLeadingEmptyLines() throws Exception { } @Test - public void testMessageParsingTimeout() throws Exception { + void testMessageParsingTimeout() throws Exception { final String s = "HTTP\000/1.1 200\000 OK\r\n" + "Server: wha\000tever\r\n" + diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestSessionInOutBuffers.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestSessionInOutBuffers.java index e6b25b79fb..5494306066 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestSessionInOutBuffers.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestSessionInOutBuffers.java @@ -48,10 +48,10 @@ import org.mockito.ArgumentMatchers; import org.mockito.Mockito; -public class TestSessionInOutBuffers { +class TestSessionInOutBuffers { @Test - public void testBasicBufferProperties() throws Exception { + 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()); @@ -72,7 +72,7 @@ public void testBasicBufferProperties() throws Exception { } @Test - public void testBasicReadWriteLine() throws Exception { + void testBasicReadWriteLine() throws Exception { final String[] teststrs = new String[5]; teststrs[0] = "Hello"; @@ -126,7 +126,7 @@ public void testBasicReadWriteLine() throws Exception { } @Test - public void testComplexReadWriteLine() throws Exception { + void testComplexReadWriteLine() throws Exception { final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); outbuffer.write(new byte[] {'a', '\n'}, outputStream); @@ -219,7 +219,7 @@ public void testComplexReadWriteLine() throws Exception { } @Test - public void testBasicReadWriteLineLargeBuffer() throws Exception { + void testBasicReadWriteLineLargeBuffer() throws Exception { final String[] teststrs = new String[5]; teststrs[0] = "Hello"; @@ -270,7 +270,7 @@ public void testBasicReadWriteLineLargeBuffer() throws Exception { } @Test - public void testReadWriteBytes() throws Exception { + 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++) { @@ -333,7 +333,7 @@ public void testReadWriteBytes() throws Exception { } @Test - public void testReadWriteByte() throws Exception { + 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++) { @@ -370,7 +370,7 @@ public void testReadWriteByte() throws Exception { } @Test - public void testWriteSmallFragmentBuffering() throws Exception { + 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); @@ -383,7 +383,7 @@ public void testWriteSmallFragmentBuffering() throws Exception { } @Test - public void testWriteSmallFragmentNoBuffering() throws Exception { + 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); @@ -395,7 +395,7 @@ public void testWriteSmallFragmentNoBuffering() throws Exception { } @Test - public void testLineLimit() throws Exception { + void testLineLimit() throws Exception { final String s = "a very looooooooooooooooooooooooooooooooooooooooooong line\r\n"; final byte[] tmp = s.getBytes(StandardCharsets.US_ASCII); // no limit @@ -415,7 +415,7 @@ public void testLineLimit() throws Exception { } @Test - public void testLineLimit2() throws Exception { + void testLineLimit2() throws Exception { final String s = "just a line\r\n"; final byte[] tmp = s.getBytes(StandardCharsets.US_ASCII); // no limit @@ -435,7 +435,7 @@ public void testLineLimit2() throws Exception { } @Test //HTTPCORE-472 - public void testLineLimit3() throws Exception { + void testLineLimit3() throws Exception { final String s = "012345678\r\nblaaaaaaaaaaaaaaaaaah"; final byte[] tmp = s.getBytes(StandardCharsets.US_ASCII); final SessionInputBuffer inBuffer1 = new SessionInputBufferImpl(128); @@ -446,7 +446,7 @@ public void testLineLimit3() throws Exception { } @Test - public void testReadLineFringeCase1() throws Exception { + void testReadLineFringeCase1() throws Exception { final String s = "abc\r\n"; final byte[] tmp = s.getBytes(StandardCharsets.US_ASCII); final SessionInputBuffer inBuffer1 = new SessionInputBufferImpl(128); @@ -479,7 +479,7 @@ private static String constructString(final int [] unicodeChars) { } @Test - public void testMultibyteCodedReadWriteLine() throws Exception { + void testMultibyteCodedReadWriteLine() throws Exception { final String s1 = constructString(SWISS_GERMAN_HELLO); final String s2 = constructString(RUSSIAN_HELLO); final String s3 = "Like hello and stuff"; @@ -529,7 +529,7 @@ public void testMultibyteCodedReadWriteLine() throws Exception { } @Test - public void testMultibyteCodedReadWriteLongLine() throws Exception { + void testMultibyteCodedReadWriteLongLine() throws Exception { final String s1 = constructString(SWISS_GERMAN_HELLO); final String s2 = constructString(RUSSIAN_HELLO); final String s3 = "Like hello and stuff"; @@ -556,10 +556,10 @@ public void testMultibyteCodedReadWriteLongLine() throws Exception { } @Test - public void testNonAsciiReadWriteLine() throws Exception { + void testNonAsciiReadWriteLine() throws Exception { final String s1 = constructString(SWISS_GERMAN_HELLO); - final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16, StandardCharsets.ISO_8859_1.newEncoder()); + final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16, StandardCharsets.UTF_8.newEncoder()); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); @@ -572,10 +572,10 @@ public void testNonAsciiReadWriteLine() throws Exception { outbuffer.writeLine(chbuffer, outputStream); outbuffer.flush(outputStream); final long bytesWritten = outbuffer.getMetrics().getBytesTransferred(); - final long expected = ((s1.getBytes(StandardCharsets.ISO_8859_1).length + 2)) * 10 + 2; + final long expected = ((s1.getBytes(StandardCharsets.UTF_8).length + 2)) * 10 + 2; Assertions.assertEquals(expected, bytesWritten); - final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.ISO_8859_1.newDecoder()); + final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.UTF_8.newDecoder()); final ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); for (int i = 0; i < 10; i++) { @@ -595,7 +595,7 @@ public void testNonAsciiReadWriteLine() throws Exception { } @Test - public void testUnmappableInputActionReport() throws Exception { + void testUnmappableInputActionReport() { final String s = "This text contains a circumflex \u0302 !!!"; final CharsetEncoder encoder = StandardCharsets.ISO_8859_1.newEncoder(); encoder.onMalformedInput(CodingErrorAction.IGNORE); @@ -609,7 +609,7 @@ public void testUnmappableInputActionReport() throws Exception { } @Test - public void testUnmappableInputActionReplace() throws Exception { + 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); @@ -625,7 +625,7 @@ public void testUnmappableInputActionReplace() throws Exception { } @Test - public void testUnmappableInputActionIgnore() throws Exception { + 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); @@ -641,7 +641,7 @@ public void testUnmappableInputActionIgnore() throws Exception { } @Test - public void testMalformedInputActionReport() throws Exception { + 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); @@ -654,7 +654,7 @@ public void testMalformedInputActionReport() throws Exception { } @Test - public void testMalformedInputActionReplace() throws Exception { + 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); @@ -667,7 +667,7 @@ public void testMalformedInputActionReplace() throws Exception { } @Test - public void testMalformedInputActionIgnore() throws Exception { + 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); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TimeoutByteArrayInputStream.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TimeoutByteArrayInputStream.java index ba87dc5679..b27632d40c 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TimeoutByteArrayInputStream.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TimeoutByteArrayInputStream.java @@ -32,7 +32,7 @@ import java.io.InterruptedIOException; /** - * Test class similar to {@link java.io.ByteArrayInputStream} that throws if encounters + * Test class similar to {@link ByteArrayInputStream} that throws if encounters * value zero '\000' in the source byte array. */ class TimeoutByteArrayInputStream extends InputStream { @@ -73,6 +73,9 @@ public int read(final byte b[], final int off, final int len) throws IOException ((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; } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestAbstractHttp1StreamDuplexerCapacityWindow.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestAbstractHttp1StreamDuplexerCapacityWindow.java index 6fa26ce414..2f85e4434d 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestAbstractHttp1StreamDuplexerCapacityWindow.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestAbstractHttp1StreamDuplexerCapacityWindow.java @@ -37,11 +37,12 @@ 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; -public class TestAbstractHttp1StreamDuplexerCapacityWindow { +class TestAbstractHttp1StreamDuplexerCapacityWindow { @Mock private IOSession ioSession; @@ -49,35 +50,35 @@ public class TestAbstractHttp1StreamDuplexerCapacityWindow { private AutoCloseable closeable; @BeforeEach - public void prepareMocks() { + void prepareMocks() { closeable = MockitoAnnotations.openMocks(this); } @AfterEach - public void releaseMocks() throws Exception { + void releaseMocks() throws Exception { closeable.close(); } @Test - public void testWindowUpdate() throws IOException { + void testWindowUpdate() throws IOException { final CapacityWindow window = new CapacityWindow(0, ioSession); window.update(1); Assertions.assertEquals(1, window.getWindow()); - Mockito.verify(ioSession).setEvent(Mockito.eq(SelectionKey.OP_READ)); + Mockito.verify(ioSession).setEvent(ArgumentMatchers.eq(SelectionKey.OP_READ)); Mockito.verifyNoMoreInteractions(ioSession); } @Test - public void testRemoveCapacity() { + void testRemoveCapacity() { final CapacityWindow window = new CapacityWindow(1, ioSession); window.removeCapacity(1); Assertions.assertEquals(0, window.getWindow()); - Mockito.verify(ioSession).clearEvent(Mockito.eq(SelectionKey.OP_READ)); + Mockito.verify(ioSession).clearEvent(ArgumentMatchers.eq(SelectionKey.OP_READ)); Mockito.verifyNoMoreInteractions(ioSession); } @Test - public void noReadsSetAfterWindowIsClosed() throws IOException { + void noReadsSetAfterWindowIsClosed() throws IOException { final CapacityWindow window = new CapacityWindow(1, ioSession); window.close(); window.update(1); @@ -85,21 +86,21 @@ public void noReadsSetAfterWindowIsClosed() throws IOException { } @Test - public void windowCannotUnderflow() { + void windowCannotUnderflow() { final CapacityWindow window = new CapacityWindow(Integer.MIN_VALUE, ioSession); window.removeCapacity(1); Assertions.assertEquals(Integer.MIN_VALUE, window.getWindow()); } @Test - public void windowCannotOverflow() throws IOException{ + void windowCannotOverflow() throws IOException{ final CapacityWindow window = new CapacityWindow(Integer.MAX_VALUE, ioSession); window.update(1); Assertions.assertEquals(Integer.MAX_VALUE, window.getWindow()); } @Test - public void noChangesIfUpdateIsNonPositive() throws IOException { + void noChangesIfUpdateIsNonPositive() throws IOException { final CapacityWindow window = new CapacityWindow(1, ioSession); window.update(0); window.update(-1); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestChunkDecoder.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestChunkDecoder.java index 768c4cc41e..4e6eed6ea2 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestChunkDecoder.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestChunkDecoder.java @@ -48,10 +48,10 @@ /** * Simple tests for {@link ChunkDecoder}. */ -public class TestChunkDecoder { +class TestChunkDecoder { @Test - public void testBasicDecoding() throws Exception { + 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); @@ -75,7 +75,7 @@ public void testBasicDecoding() throws Exception { } @Test - public void testComplexDecoding() throws Exception { + 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( @@ -112,7 +112,7 @@ public void testComplexDecoding() throws Exception { } @Test - public void testDecodingWithSmallBuffer() throws Exception { + 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( @@ -147,7 +147,7 @@ public void testDecodingWithSmallBuffer() throws Exception { } @Test - public void testMalformedChunk() throws Exception { + void testMalformedChunk() { final String s = "5\r\n01234----------------------------------------------------------" + "-----------------------------------------------------------------------------" + "-----------------------------------------------------------------------------"; @@ -164,7 +164,7 @@ public void testMalformedChunk() throws Exception { } @Test - public void testIncompleteChunkDecoding() throws Exception { + void testIncompleteChunkDecoding() throws Exception { final String[] chunks = { "10;", "key=\"value\"\r", @@ -211,7 +211,7 @@ public void testIncompleteChunkDecoding() throws Exception { } @Test - public void testMalformedChunkSizeDecoding() throws Exception { + 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); @@ -226,7 +226,7 @@ public void testMalformedChunkSizeDecoding() throws Exception { } @Test - public void testMalformedChunkEndingDecoding() throws Exception { + 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); @@ -241,7 +241,7 @@ public void testMalformedChunkEndingDecoding() throws Exception { } @Test - public void testMalformedChunkTruncatedChunk() throws Exception { + void testMalformedChunkTruncatedChunk() throws Exception { final String s = "3\r\n12"; final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); @@ -257,7 +257,7 @@ public void testMalformedChunkTruncatedChunk() throws Exception { } @Test - public void testFoldedFooters() throws Exception { + 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( @@ -280,7 +280,7 @@ public void testFoldedFooters() throws Exception { } @Test - public void testMalformedFooters() throws Exception { + 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( @@ -296,7 +296,7 @@ public void testMalformedFooters() throws Exception { } @Test - public void testMissingLastCRLF() throws Exception { + void testMissingLastCRLF() { final String s = "10\r\n1234567890123456\r\n" + "5\r\n12345\r\n5\r\n12345"; final ReadableByteChannel channel = new ReadableByteChannelMock( @@ -316,7 +316,7 @@ public void testMissingLastCRLF() throws Exception { } @Test - public void testMissingClosingChunk() throws Exception { + void testMissingClosingChunk() { final String s = "10\r\n1234567890123456\r\n" + "5\r\n12345\r\n5\r\n12345\r\n"; final ReadableByteChannel channel = new ReadableByteChannelMock( @@ -347,7 +347,7 @@ public void testMissingClosingChunk() throws Exception { } @Test - public void testReadingWitSmallBuffer() throws Exception { + void testReadingWitSmallBuffer() throws Exception { final String s = "10\r\n1234567890123456\r\n" + "40\r\n12345678901234561234567890123456" + "12345678901234561234567890123456\r\n0\r\n"; @@ -380,7 +380,7 @@ public void testReadingWitSmallBuffer() throws Exception { } @Test - public void testEndOfStreamConditionReadingFooters() throws Exception { + 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( @@ -406,7 +406,7 @@ public void testEndOfStreamConditionReadingFooters() throws Exception { } @Test - public void testTooLongChunkHeader() throws Exception { + 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); @@ -435,7 +435,7 @@ public void testTooLongChunkHeader() throws Exception { } @Test - public void testTooLongFooter() throws Exception { + 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( @@ -465,7 +465,7 @@ public void testTooLongFooter() throws Exception { } @Test - public void testTooLongFoldedFooter() throws Exception { + 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( @@ -498,7 +498,7 @@ public void testTooLongFoldedFooter() throws Exception { } @Test - public void testTooManyFooters() throws Exception { + 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( @@ -530,7 +530,7 @@ public void testTooManyFooters() throws Exception { } @Test - public void testInvalidConstructor() { + void testInvalidConstructor() { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII); @@ -540,7 +540,7 @@ public void testInvalidConstructor() { } @Test - public void testInvalidInput() throws Exception { + 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( @@ -554,7 +554,7 @@ public void testInvalidInput() throws Exception { } @Test - public void testHugeChunk() throws Exception { + 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); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestChunkEncoder.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestChunkEncoder.java index dbca992803..29b8fd13b6 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestChunkEncoder.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestChunkEncoder.java @@ -44,10 +44,10 @@ /** * Simple tests for {@link ChunkEncoder}. */ -public class TestChunkEncoder { +class TestChunkEncoder { @Test - public void testBasicCoding() throws Exception { + void testBasicCoding() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -68,7 +68,7 @@ public void testBasicCoding() throws Exception { } @Test - public void testChunkNoExceed() throws Exception { + void testChunkNoExceed() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 16); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -85,7 +85,7 @@ public void testChunkNoExceed() throws Exception { } @Test // See HTTPCORE-239 - public void testLimitedChannel() throws Exception { + void testLimitedChannel() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(16, 16); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(16, 16); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -118,7 +118,7 @@ public void testLimitedChannel() throws Exception { } @Test - public void testBufferFragments() throws Exception { + void testBufferFragments() throws Exception { final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(1024)); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 1024); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -137,7 +137,7 @@ public void testBufferFragments() throws Exception { } @Test - public void testChunkExceed() throws Exception { + void testChunkExceed() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(16, 16); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -155,7 +155,7 @@ public void testChunkExceed() throws Exception { } @Test - public void testCodingEmptyBuffer() throws Exception { + void testCodingEmptyBuffer() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -181,7 +181,7 @@ public void testCodingEmptyBuffer() throws Exception { } @Test - public void testCodingCompleted() throws Exception { + void testCodingCompleted() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -192,12 +192,14 @@ public void testCodingCompleted() throws Exception { encoder.write(CodecTestUtils.wrap("90")); encoder.complete(); - Assertions.assertThrows(IllegalStateException.class, () -> encoder.write(CodecTestUtils.wrap("more stuff"))); + final ByteBuffer wrapped = CodecTestUtils.wrap("more stuff"); + + Assertions.assertThrows(IllegalStateException.class, () -> encoder.write(wrapped)); Assertions.assertThrows(IllegalStateException.class, () -> encoder.complete()); } @Test - public void testInvalidConstructor() { + void testInvalidConstructor() { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); @@ -207,7 +209,7 @@ public void testInvalidConstructor() { } @Test - public void testTrailers() throws IOException { + void testTrailers() throws IOException { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestExpandableBuffer.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestExpandableBuffer.java index d3c4d22e30..2193c3e7bd 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestExpandableBuffer.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestExpandableBuffer.java @@ -32,10 +32,10 @@ import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; -public class TestExpandableBuffer { +class TestExpandableBuffer { @Test - public void testBasics() throws Exception { + void testBasics() { final ExpandableBuffer buffer = new ExpandableBuffer(16); assertThat(buffer.mode(), CoreMatchers.equalTo(ExpandableBuffer.Mode.INPUT)); assertThat(buffer.hasData(), CoreMatchers.equalTo(false)); @@ -67,7 +67,7 @@ public void testBasics() throws Exception { } @Test - public void testAdjustCapacity() throws Exception { + void testAdjustCapacity() { final ExpandableBuffer buffer = new ExpandableBuffer(16); assertThat(buffer.capacity(), CoreMatchers.equalTo(16)); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestIdentityDecoder.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestIdentityDecoder.java index 227c87a300..f007a72491 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestIdentityDecoder.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestIdentityDecoder.java @@ -45,7 +45,7 @@ /** * Simple tests for {@link LengthDelimitedDecoder}. */ -public class TestIdentityDecoder { +class TestIdentityDecoder { private File tmpfile; @@ -55,14 +55,14 @@ protected File createTempFile() throws IOException { } @AfterEach - public void deleteTempFile() { + void deleteTempFile() { if (this.tmpfile != null && this.tmpfile.exists()) { this.tmpfile.delete(); } } @Test - public void testBasicDecoding() throws Exception { + void testBasicDecoding() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII); @@ -101,7 +101,7 @@ public void testBasicDecoding() throws Exception { } @Test - public void testDecodingFromSessionBuffer() throws Exception { + void testDecodingFromSessionBuffer() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII); @@ -144,7 +144,7 @@ public void testDecodingFromSessionBuffer() throws Exception { } @Test - public void testBasicDecodingFile() throws Exception { + void testBasicDecodingFile() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff; ", "more stuff; ", "a lot more stuff!"}, StandardCharsets.US_ASCII); @@ -171,7 +171,7 @@ public void testBasicDecodingFile() throws Exception { } @Test - public void testDecodingFileWithBufferedSessionData() throws Exception { + void testDecodingFileWithBufferedSessionData() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff; ", "more stuff; ", "a lot more stuff!"}, StandardCharsets.US_ASCII); @@ -202,7 +202,7 @@ public void testDecodingFileWithBufferedSessionData() throws Exception { } @Test - public void testDecodingFileWithOffsetAndBufferedSessionData() throws Exception { + void testDecodingFileWithOffsetAndBufferedSessionData() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff; ", "more stuff; ", "a lot more stuff!"}, StandardCharsets.US_ASCII); @@ -249,7 +249,7 @@ public void testDecodingFileWithOffsetAndBufferedSessionData() throws Exception } @Test - public void testDecodingFileWithLimit() throws Exception { + void testDecodingFileWithLimit() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff; more stuff; ", "a lot more stuff!"}, StandardCharsets.US_ASCII); @@ -313,7 +313,7 @@ public void testDecodingFileWithLimit() throws Exception { } @Test - public void testWriteBeyondFileSize() throws Exception { + void testWriteBeyondFileSize() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"a"}, StandardCharsets.US_ASCII); @@ -331,7 +331,7 @@ public void testWriteBeyondFileSize() throws Exception { } @Test - public void testInvalidConstructor() { + void testInvalidConstructor() { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII); @@ -342,7 +342,7 @@ public void testInvalidConstructor() { } @Test - public void testInvalidInput() throws Exception { + void testInvalidInput() { final String s = "stuff"; final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestIdentityEncoder.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestIdentityEncoder.java index f5c073da82..38f2b65257 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestIdentityEncoder.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestIdentityEncoder.java @@ -47,7 +47,7 @@ /** * Simple tests for {@link IdentityEncoder}. */ -public class TestIdentityEncoder { +class TestIdentityEncoder { private File tmpfile; @@ -57,14 +57,14 @@ protected File createTempFile() throws IOException { } @AfterEach - public void deleteTempFile() { + void deleteTempFile() { if (this.tmpfile != null && this.tmpfile.exists()) { this.tmpfile.delete(); } } @Test - public void testBasicCoding() throws Exception { + void testBasicCoding() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -84,7 +84,7 @@ public void testBasicCoding() throws Exception { } @Test - public void testCodingEmptySrcBuffer() throws Exception { + void testCodingEmptySrcBuffer() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -106,7 +106,7 @@ public void testCodingEmptySrcBuffer() throws Exception { } @Test - public void testCodingCompleted() throws Exception { + void testCodingCompleted() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -115,11 +115,13 @@ public void testCodingCompleted() throws Exception { encoder.write(CodecTestUtils.wrap("stuff")); encoder.complete(); - Assertions.assertThrows(IllegalStateException.class, () -> encoder.write(CodecTestUtils.wrap("more stuff"))); + final ByteBuffer wrapped = CodecTestUtils.wrap("more stuff"); + + Assertions.assertThrows(IllegalStateException.class, () -> encoder.write(wrapped)); } @Test - public void testInvalidConstructor() { + void testInvalidConstructor() { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); @@ -129,7 +131,7 @@ public void testInvalidConstructor() { } @Test - public void testCodingFromFile() throws Exception { + void testCodingFromFile() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -160,7 +162,7 @@ public void testCodingFromFile() throws Exception { } @Test - public void testCodingEmptyFile() throws Exception { + void testCodingEmptyFile() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -189,37 +191,7 @@ public void testCodingEmptyFile() throws Exception { } @Test - public void testCodingFromFileSmaller() 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 - public void testCodingFromFileFlushBuffer() throws Exception { + void testCodingFromFileFlushBuffer() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -253,7 +225,7 @@ public void testCodingFromFileFlushBuffer() throws Exception { } @Test - public void testCodingFromFileChannelSaturated() throws Exception { + void testCodingFromFileChannelSaturated() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64, 4); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -287,7 +259,7 @@ public void testCodingFromFileChannelSaturated() throws Exception { } @Test - public void testCodingNoFragmentBuffering() throws Exception { + 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(); @@ -311,7 +283,7 @@ public void testCodingNoFragmentBuffering() throws Exception { } @Test - public void testCodingFragmentBuffering() throws Exception { + 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(); @@ -335,7 +307,7 @@ public void testCodingFragmentBuffering() throws Exception { } @Test - public void testCodingFragmentBufferingMultipleFragments() throws Exception { + 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(); @@ -358,7 +330,7 @@ public void testCodingFragmentBufferingMultipleFragments() throws Exception { } @Test - public void testCodingFragmentBufferingLargeFragment() throws Exception { + 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(); @@ -381,7 +353,7 @@ public void testCodingFragmentBufferingLargeFragment() throws Exception { } @Test - public void testCodingFragmentBufferingTinyFragments() throws Exception { + 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(); @@ -406,7 +378,7 @@ public void testCodingFragmentBufferingTinyFragments() throws Exception { } @Test - public void testCodingFragmentBufferingTinyFragments2() throws Exception { + 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(); @@ -431,7 +403,7 @@ public void testCodingFragmentBufferingTinyFragments2() throws Exception { } @Test - public void testCodingFragmentBufferingTinyFragments3() throws Exception { + 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(); @@ -458,7 +430,7 @@ public void testCodingFragmentBufferingTinyFragments3() throws Exception { } @Test - public void testCodingFragmentBufferingBufferFlush() throws Exception { + 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(); @@ -481,7 +453,7 @@ public void testCodingFragmentBufferingBufferFlush() throws Exception { } @Test - public void testCodingFragmentBufferingBufferFlush2() throws Exception { + 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(); @@ -504,7 +476,7 @@ public void testCodingFragmentBufferingBufferFlush2() throws Exception { } @Test - public void testCodingFragmentBufferingChannelSaturated() throws Exception { + 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(); @@ -534,7 +506,7 @@ public void testCodingFragmentBufferingChannelSaturated() throws Exception { } @Test - public void testCodingFragmentBufferingChannelSaturated2() throws Exception { + 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(); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestLengthDelimitedDecoder.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestLengthDelimitedDecoder.java index ed22e85b42..4713672ea5 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestLengthDelimitedDecoder.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestLengthDelimitedDecoder.java @@ -46,7 +46,7 @@ /** * Simple tests for {@link LengthDelimitedDecoder}. */ -public class TestLengthDelimitedDecoder { +class TestLengthDelimitedDecoder { private File tmpfile; @@ -56,14 +56,14 @@ protected File createTempFile() throws IOException { } @AfterEach - public void deleteTempFile() { + void deleteTempFile() { if (this.tmpfile != null && this.tmpfile.exists()) { this.tmpfile.delete(); } } @Test - public void testBasicDecoding() throws Exception { + void testBasicDecoding() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII); @@ -97,7 +97,7 @@ public void testBasicDecoding() throws Exception { } @Test - public void testCodingBeyondContentLimit() throws Exception { + void testCodingBeyondContentLimit() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] { "stuff;", @@ -131,7 +131,7 @@ public void testCodingBeyondContentLimit() throws Exception { } @Test - public void testBasicDecodingSmallBuffer() throws Exception { + void testBasicDecodingSmallBuffer() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII); @@ -184,7 +184,7 @@ public void testBasicDecodingSmallBuffer() throws Exception { } @Test - public void testDecodingFromSessionBuffer1() throws Exception { + void testDecodingFromSessionBuffer1() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII); @@ -221,7 +221,7 @@ public void testDecodingFromSessionBuffer1() throws Exception { } @Test - public void testDecodingFromSessionBuffer2() throws Exception { + void testDecodingFromSessionBuffer2() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] { "stuff;", @@ -255,7 +255,7 @@ public void testDecodingFromSessionBuffer2() throws Exception { /* ----------------- FileChannel Part testing --------------------------- */ @Test - public void testBasicDecodingFile() throws Exception { + void testBasicDecodingFile() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff; ", "more stuff; ", "a lot more stuff!!!"}, StandardCharsets.US_ASCII); @@ -281,7 +281,7 @@ public void testBasicDecodingFile() throws Exception { } @Test - public void testDecodingFileWithBufferedSessionData() throws Exception { + void testDecodingFileWithBufferedSessionData() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff; ", "more stuff; ", "a lot more stuff!!!"}, StandardCharsets.US_ASCII); @@ -310,7 +310,7 @@ public void testDecodingFileWithBufferedSessionData() throws Exception { } @Test - public void testDecodingFileWithOffsetAndBufferedSessionData() throws Exception { + void testDecodingFileWithOffsetAndBufferedSessionData() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff; ", "more stuff; ", "a lot more stuff!"}, StandardCharsets.US_ASCII); @@ -357,7 +357,7 @@ public void testDecodingFileWithOffsetAndBufferedSessionData() throws Exception } @Test - public void testDecodingFileWithLimit() throws Exception { + void testDecodingFileWithLimit() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff; more stuff; ", "a lot more stuff!!!"}, StandardCharsets.US_ASCII); @@ -421,7 +421,7 @@ public void testDecodingFileWithLimit() throws Exception { } @Test - public void testWriteBeyondFileSize() throws Exception { + void testWriteBeyondFileSize() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"a"}, StandardCharsets.US_ASCII); @@ -439,7 +439,7 @@ public void testWriteBeyondFileSize() throws Exception { } @Test - public void testCodingBeyondContentLimitFile() throws Exception { + void testCodingBeyondContentLimitFile() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] { "stuff;", @@ -472,7 +472,7 @@ public void testCodingBeyondContentLimitFile() throws Exception { } @Test - public void testInvalidConstructor() { + void testInvalidConstructor() { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII); @@ -485,7 +485,7 @@ public void testInvalidConstructor() { } @Test - public void testInvalidInput() throws Exception { + void testInvalidInput() { final String s = "stuff"; final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {s}, StandardCharsets.US_ASCII); @@ -499,7 +499,7 @@ public void testInvalidInput() throws Exception { } @Test - public void testZeroLengthDecoding() throws Exception { + void testZeroLengthDecoding() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"stuff"}, StandardCharsets.US_ASCII); @@ -517,7 +517,7 @@ public void testZeroLengthDecoding() throws Exception { } @Test - public void testTruncatedContent() throws Exception { + void testTruncatedContent() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"1234567890"}, StandardCharsets.US_ASCII); @@ -535,7 +535,7 @@ public void testTruncatedContent() throws Exception { } @Test - public void testTruncatedContentWithFile() throws Exception { + void testTruncatedContentWithFile() throws Exception { final ReadableByteChannel channel = new ReadableByteChannelMock( new String[] {"1234567890"}, StandardCharsets.US_ASCII); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestLengthDelimitedEncoder.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestLengthDelimitedEncoder.java index 6931a961cf..927bb83392 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestLengthDelimitedEncoder.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestLengthDelimitedEncoder.java @@ -47,7 +47,7 @@ /** * Simple tests for {@link LengthDelimitedEncoder}. */ -public class TestLengthDelimitedEncoder { +class TestLengthDelimitedEncoder { private File tmpfile; @@ -57,14 +57,14 @@ protected File createTempFile() throws IOException { } @AfterEach - public void deleteTempFile() { + void deleteTempFile() { if (this.tmpfile != null && this.tmpfile.exists()) { this.tmpfile.delete(); } } @Test - public void testBasicCoding() throws Exception { + void testBasicCoding() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -82,7 +82,7 @@ public void testBasicCoding() throws Exception { } @Test - public void testCodingBeyondContentLimit() throws Exception { + void testCodingBeyondContentLimit() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -99,7 +99,7 @@ public void testCodingBeyondContentLimit() throws Exception { } @Test - public void testCodingEmptyBuffer() throws Exception { + void testCodingEmptyBuffer() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -122,7 +122,7 @@ public void testCodingEmptyBuffer() throws Exception { } @Test - public void testCodingCompleted() throws Exception { + void testCodingCompleted() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -131,11 +131,13 @@ public void testCodingCompleted() throws Exception { channel, outbuf, metrics, 5); encoder.write(CodecTestUtils.wrap("stuff")); - Assertions.assertThrows(IllegalStateException.class, () -> encoder.write(CodecTestUtils.wrap("more stuff"))); + final ByteBuffer wrapped = CodecTestUtils.wrap("more stuff"); + + Assertions.assertThrows(IllegalStateException.class, () -> encoder.write(wrapped)); } @Test - public void testInvalidConstructor() { + void testInvalidConstructor() { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -147,7 +149,7 @@ public void testInvalidConstructor() { } @Test - public void testCodingBeyondContentLimitFromFile() throws Exception { + void testCodingBeyondContentLimitFromFile() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -179,7 +181,7 @@ public void testCodingBeyondContentLimitFromFile() throws Exception { } @Test - public void testCodingEmptyFile() throws Exception { + void testCodingEmptyFile() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -209,7 +211,7 @@ public void testCodingEmptyFile() throws Exception { } @Test - public void testCodingCompletedFromFile() throws Exception { + void testCodingCompletedFromFile() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -230,7 +232,7 @@ public void testCodingCompletedFromFile() throws Exception { } @Test - public void testCodingFromFileSmaller() throws Exception { + void testCodingFromFileSmaller() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -261,7 +263,7 @@ public void testCodingFromFileSmaller() throws Exception { } @Test - public void testCodingFromFileFlushBuffer() throws Exception { + void testCodingFromFileFlushBuffer() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -296,7 +298,7 @@ public void testCodingFromFileFlushBuffer() throws Exception { } @Test - public void testCodingFromFileChannelSaturated() throws Exception { + void testCodingFromFileChannelSaturated() throws Exception { final WritableByteChannelMock channel = new WritableByteChannelMock(64, 4); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128); final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics(); @@ -331,7 +333,7 @@ public void testCodingFromFileChannelSaturated() throws Exception { } @Test - public void testCodingNoFragmentBuffering() throws Exception { + 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(); @@ -356,7 +358,7 @@ public void testCodingNoFragmentBuffering() throws Exception { } @Test - public void testCodingFragmentBuffering() throws Exception { + 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(); @@ -381,7 +383,7 @@ public void testCodingFragmentBuffering() throws Exception { } @Test - public void testCodingFragmentBufferingMultipleFragments() throws Exception { + 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(); @@ -405,7 +407,7 @@ public void testCodingFragmentBufferingMultipleFragments() throws Exception { } @Test - public void testCodingFragmentBufferingMultipleFragmentsBeyondContentLimit() throws Exception { + 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(); @@ -429,7 +431,7 @@ public void testCodingFragmentBufferingMultipleFragmentsBeyondContentLimit() thr } @Test - public void testCodingFragmentBufferingLargeFragment() throws Exception { + 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(); @@ -453,7 +455,7 @@ public void testCodingFragmentBufferingLargeFragment() throws Exception { } @Test - public void testCodingFragmentBufferingTinyFragments() throws Exception { + 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(); @@ -479,7 +481,7 @@ public void testCodingFragmentBufferingTinyFragments() throws Exception { } @Test - public void testCodingFragmentBufferingTinyFragments2() throws Exception { + 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(); @@ -505,7 +507,7 @@ public void testCodingFragmentBufferingTinyFragments2() throws Exception { } @Test - public void testCodingFragmentBufferingTinyFragments3() throws Exception { + 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(); @@ -533,7 +535,7 @@ public void testCodingFragmentBufferingTinyFragments3() throws Exception { } @Test - public void testCodingFragmentBufferingBufferFlush() throws Exception { + 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(); @@ -557,7 +559,7 @@ public void testCodingFragmentBufferingBufferFlush() throws Exception { } @Test - public void testCodingFragmentBufferingBufferFlush2() throws Exception { + 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(); @@ -581,7 +583,7 @@ public void testCodingFragmentBufferingBufferFlush2() throws Exception { } @Test - public void testCodingFragmentBufferingChannelSaturated() throws Exception { + 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(); @@ -612,7 +614,7 @@ public void testCodingFragmentBufferingChannelSaturated() throws Exception { } @Test - public void testCodingFragmentBufferingChannelSaturated2() throws Exception { + 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(); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestSessionInOutBuffers.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestSessionInOutBuffers.java index 0f4d1bba89..9344c0f4e3 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestSessionInOutBuffers.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/TestSessionInOutBuffers.java @@ -50,7 +50,7 @@ /** * Simple tests for {@link SessionInputBuffer} and {@link SessionOutputBuffer}. */ -public class TestSessionInOutBuffers { +class TestSessionInOutBuffers { private static WritableByteChannel newChannel(final ByteArrayOutputStream outStream) { return Channels.newChannel(outStream); @@ -69,7 +69,7 @@ private static ReadableByteChannel newChannel(final String s) { } @Test - public void testReadLineChunks() throws Exception { + void testReadLineChunks() throws Exception { final SessionInputBuffer inbuf = new SessionInputBufferImpl(16, 16); ReadableByteChannel channel = newChannel("One\r\nTwo\r\nThree"); @@ -107,7 +107,7 @@ public void testReadLineChunks() throws Exception { } @Test - public void testLineLimit() throws Exception { + void testLineLimit() throws Exception { final String s = "LoooooooooooooooooooooooooOOOOOOOOOOOOOOOOOOoooooooooooooooooooooong line\r\n"; final CharArrayBuffer line = new CharArrayBuffer(64); final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(128, 128); @@ -124,7 +124,7 @@ public void testLineLimit() throws Exception { } @Test - public void testLineLimitBufferFull() throws Exception { + void testLineLimitBufferFull() throws Exception { final String s = "LoooooooooooooooooooooooooOOOOOOOOOOOOOOOOOOoooooooooooooooooooooong line\r\n"; final CharArrayBuffer line = new CharArrayBuffer(64); final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(32, 32); @@ -141,7 +141,7 @@ public void testLineLimitBufferFull() throws Exception { } @Test - public void testWriteLineChunks() throws Exception { + void testWriteLineChunks() throws Exception { final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(16, 16); final SessionInputBuffer inbuf = new SessionInputBufferImpl(16, 16, 0); @@ -195,7 +195,7 @@ public void testWriteLineChunks() throws Exception { } @Test - public void testNonASCIIWriteLine() throws Exception { + 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"; @@ -223,7 +223,7 @@ public void testNonASCIIWriteLine() throws Exception { } @Test - public void testBasicReadWriteLine() throws Exception { + void testBasicReadWriteLine() throws Exception { final String[] teststrs = new String[5]; teststrs[0] = "Hello"; @@ -269,7 +269,7 @@ public void testBasicReadWriteLine() throws Exception { } @Test - public void testComplexReadWriteLine() throws Exception { + 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'})); @@ -345,7 +345,7 @@ public void testComplexReadWriteLine() throws Exception { } @Test - public void testReadOneByte() throws Exception { + 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++) { @@ -366,7 +366,7 @@ public void testReadOneByte() throws Exception { } @Test - public void testReadByteBuffer() throws Exception { + 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); @@ -383,7 +383,7 @@ public void testReadByteBuffer() throws Exception { } @Test - public void testReadByteBufferWithMaxLen() throws Exception { + 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); @@ -403,7 +403,7 @@ public void testReadByteBufferWithMaxLen() throws Exception { } @Test - public void testReadToChannel() throws Exception { + 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); @@ -418,7 +418,7 @@ public void testReadToChannel() throws Exception { } @Test - public void testReadToChannelWithMaxLen() throws Exception { + 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); @@ -435,7 +435,7 @@ public void testReadToChannelWithMaxLen() throws Exception { } @Test - public void testWriteByteBuffer() throws Exception { + void testWriteByteBuffer() throws Exception { final byte[] pattern = "0123456789ABCDEF0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(4096, 1024); @@ -450,7 +450,7 @@ public void testWriteByteBuffer() throws Exception { } @Test - public void testWriteFromChannel() throws Exception { + void testWriteFromChannel() throws Exception { final byte[] pattern = "0123456789ABCDEF0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(4096, 1024); @@ -485,7 +485,7 @@ private static String constructString(final int [] unicodeChars) { } @Test - public void testMultibyteCodedReadWriteLine() throws Exception { + void testMultibyteCodedReadWriteLine() throws Exception { final String s1 = constructString(SWISS_GERMAN_HELLO); final String s2 = constructString(RUSSIAN_HELLO); final String s3 = "Like hello and stuff"; @@ -533,16 +533,16 @@ public void testMultibyteCodedReadWriteLine() throws Exception { } @Test - public void testInputMatchesBufferLength() throws Exception { + void testInputMatchesBufferLength() { final String s1 = "abcde"; final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 5); final CharArrayBuffer chbuffer = new CharArrayBuffer(16); chbuffer.append(s1); - outbuf.writeLine(chbuffer); + Assertions.assertDoesNotThrow(() -> outbuf.writeLine(chbuffer)); } @Test - public void testMalformedInputActionReport() throws Exception { + void testMalformedInputActionReport() throws Exception { final String s = constructString(SWISS_GERMAN_HELLO); final byte[] tmp = s.getBytes(StandardCharsets.ISO_8859_1); @@ -559,7 +559,7 @@ public void testMalformedInputActionReport() throws Exception { } @Test - public void testMalformedInputActionIgnore() throws Exception { + void testMalformedInputActionIgnore() throws Exception { final String s = constructString(SWISS_GERMAN_HELLO); final byte[] tmp = s.getBytes(StandardCharsets.ISO_8859_1); @@ -576,7 +576,7 @@ public void testMalformedInputActionIgnore() throws Exception { } @Test - public void testMalformedInputActionReplace() throws Exception { + void testMalformedInputActionReplace() throws Exception { final String s = constructString(SWISS_GERMAN_HELLO); final byte[] tmp = s.getBytes(StandardCharsets.ISO_8859_1); @@ -593,7 +593,7 @@ public void testMalformedInputActionReplace() throws Exception { } @Test - public void testUnmappableInputActionReport() throws Exception { + void testUnmappableInputActionReport() { final String s = "This text contains a circumflex \u0302!!!"; final CharsetEncoder encoder = StandardCharsets.ISO_8859_1.newEncoder(); encoder.onMalformedInput(CodingErrorAction.IGNORE); @@ -606,7 +606,7 @@ public void testUnmappableInputActionReport() throws Exception { } @Test - public void testUnmappableInputActionIgnore() throws Exception { + 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); @@ -624,7 +624,7 @@ public void testUnmappableInputActionIgnore() throws Exception { } @Test - public void testUnmappableInputActionReplace() throws Exception { + 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); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/routing/TestPathPatternMatcher.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/routing/TestPathPatternMatcher.java new file mode 100644 index 0000000000..e2d36876f2 --- /dev/null +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/routing/TestPathPatternMatcher.java @@ -0,0 +1,58 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * 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")); + } + + @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*")); + } + +} diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/routing/TestRequestRouter.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/routing/TestRequestRouter.java new file mode 100644 index 0000000000..3a9171a092 --- /dev/null +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/routing/TestRequestRouter.java @@ -0,0 +1,138 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * 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)); + } + +} diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/routing/TestUriPathRouter.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/routing/TestUriPathRouter.java new file mode 100644 index 0000000000..c72574db1e --- /dev/null +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/routing/TestUriPathRouter.java @@ -0,0 +1,177 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * 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 = new UriPathRouter.BestMatcher<>(); + 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 = new UriPathRouter.BestMatcher<>(); + 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 = new UriPathRouter.BestMatcher<>(); + 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 = new UriPathRouter.BestMatcher<>(); + 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 = new UriPathRouter.OrderedMatcher<>(); + 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 = new UriPathRouter.OrderedMatcher<>(); + 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 = new UriPathRouter.OrderedMatcher<>(); + 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 = new UriPathRouter.OrderedMatcher<>(); + 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 = new UriPathRouter.RegexMatcher<>(); + 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 = new UriPathRouter.RegexMatcher<>(); + final List> routes = Arrays.asList( + new PathRoute<>(Pattern.compile("/ma.*"), 1L), + new PathRoute<>(Pattern.compile(".*tch"), 2L)); + Assertions.assertEquals(1L, matcher.apply("/match", routes)); + } + +} diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/TestEofSensorInputStream.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/TestEofSensorInputStream.java index fad99a9ffb..322d3d2828 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/io/TestEofSensorInputStream.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/TestEofSensorInputStream.java @@ -35,21 +35,21 @@ import org.mockito.Mockito; @SuppressWarnings({"boxing","static-access"}) // test code -public class TestEofSensorInputStream { +class TestEofSensorInputStream { private InputStream inStream; private EofSensorWatcher eofwatcher; private EofSensorInputStream eofstream; @BeforeEach - public void setup() throws Exception { + void setup() { inStream = Mockito.mock(InputStream.class); eofwatcher = Mockito.mock(EofSensorWatcher.class); eofstream = new EofSensorInputStream(inStream, eofwatcher); } @Test - public void testClose() throws Exception { + void testClose() throws Exception { Mockito.when(eofwatcher.streamClosed(Mockito.any())).thenReturn(Boolean.TRUE); eofstream.close(); @@ -64,7 +64,7 @@ public void testClose() throws Exception { } @Test - public void testCloseIOError() throws Exception { + void testCloseIOError() throws Exception { Mockito.when(eofwatcher.streamClosed(Mockito.any())).thenThrow(new IOException()); Assertions.assertThrows(IOException.class, () -> eofstream.close()); @@ -75,22 +75,7 @@ public void testCloseIOError() throws Exception { } @Test - public void testReleaseConnection() 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 - public void testAbortConnection() throws Exception { + void testAbortConnection() throws Exception { Mockito.when(eofwatcher.streamAbort(Mockito.any())).thenReturn(Boolean.TRUE); eofstream.abort(); @@ -105,7 +90,7 @@ public void testAbortConnection() throws Exception { } @Test - public void testAbortConnectionIOError() throws Exception { + void testAbortConnectionIOError() throws Exception { Mockito.when(eofwatcher.streamAbort(Mockito.any())).thenThrow(new IOException()); Assertions.assertThrows(IOException.class, () -> eofstream.abort()); @@ -116,7 +101,7 @@ public void testAbortConnectionIOError() throws Exception { } @Test - public void testRead() throws Exception { + void testRead() throws Exception { Mockito.when(eofwatcher.eofDetected(Mockito.any())).thenReturn(Boolean.TRUE); Mockito.when(inStream.read()).thenReturn(0, -1); @@ -139,7 +124,7 @@ public void testRead() throws Exception { } @Test - public void testReadIOError() throws Exception { + void testReadIOError() throws Exception { Mockito.when(eofwatcher.eofDetected(Mockito.any())).thenReturn(Boolean.TRUE); Mockito.when(inStream.read()).thenThrow(new IOException()); @@ -151,7 +136,7 @@ public void testReadIOError() throws Exception { } @Test - public void testReadByteArray() throws Exception { + 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); @@ -177,7 +162,7 @@ public void testReadByteArray() throws Exception { } @Test - public void testReadByteArrayIOError() throws Exception { + 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()); @@ -191,7 +176,7 @@ public void testReadByteArrayIOError() throws Exception { } @Test - public void testReadAfterAbort() throws Exception { + void testReadAfterAbort() throws Exception { Mockito.when(eofwatcher.streamAbort(Mockito.any())).thenReturn(Boolean.TRUE); eofstream.abort(); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestBasicHttpEntity.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestBasicHttpEntity.java index e9dc259afa..481a086af6 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestBasicHttpEntity.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestBasicHttpEntity.java @@ -39,20 +39,43 @@ * Unit tests for {@link BasicHttpEntity}. * */ -public class TestBasicHttpEntity { +class TestBasicHttpEntity { @Test - public void testBasics() throws Exception { + 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 - public void testToString() throws Exception { + 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( @@ -62,11 +85,14 @@ public void testToString() throws Exception { } @Test - public void testWriteTo() throws Exception { + 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(); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestBufferedHttpEntity.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestBufferedHttpEntity.java index b6ae1d79bb..dc6dd4d063 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestBufferedHttpEntity.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestBufferedHttpEntity.java @@ -38,10 +38,10 @@ * Unit tests for {@link BufferedHttpEntity}. * */ -public class TestBufferedHttpEntity { +class TestBufferedHttpEntity { @Test - public void testBufferingEntity() throws Exception { + 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)); @@ -56,7 +56,7 @@ public void testBufferingEntity() throws Exception { } @Test - public void testWrappingEntity() throws Exception { + 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)) { @@ -72,12 +72,12 @@ public void testWrappingEntity() throws Exception { } @Test - public void testIllegalConstructor() throws Exception { + void testIllegalConstructor() { Assertions.assertThrows(NullPointerException.class, () -> new BufferedHttpEntity(null)); } @Test - public void testWriteToBuffered() throws Exception { + 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)) { @@ -105,7 +105,7 @@ public void testWriteToBuffered() throws Exception { } @Test - public void testWriteToWrapped() throws Exception { + 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)) { diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestByteArrayEntity.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestByteArrayEntity.java index a4b1b81126..90e957f9fe 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestByteArrayEntity.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestByteArrayEntity.java @@ -37,10 +37,10 @@ * Unit tests for {@link ByteArrayEntity}. * */ -public class TestByteArrayEntity { +class TestByteArrayEntity { @Test - public void testBasics() throws Exception { + void testBasics() throws Exception { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); try (final ByteArrayEntity entity = new ByteArrayEntity(bytes, null)) { @@ -52,7 +52,7 @@ public void testBasics() throws Exception { } @Test - public void testBasicOffLen() throws Exception { + void testBasicOffLen() throws Exception { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); try (final ByteArrayEntity entity = new ByteArrayEntity(bytes, 8, 7, null)) { @@ -64,34 +64,34 @@ public void testBasicOffLen() throws Exception { } @Test - public void testIllegalConstructorNullByteArray() throws Exception { + void testIllegalConstructorNullByteArray() { Assertions.assertThrows(NullPointerException.class, () -> new ByteArrayEntity(null, null)); } @Test - public void testIllegalConstructorBadLen() throws Exception { + void testIllegalConstructorBadLen() { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); Assertions.assertThrows(IllegalArgumentException.class, () -> new ByteArrayEntity(bytes, 0, bytes.length + 1, null)); } @Test - public void testIllegalConstructorBadOff1() throws Exception { + void testIllegalConstructorBadOff1() { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); Assertions.assertThrows(IllegalArgumentException.class, () -> new ByteArrayEntity(bytes, -1, bytes.length, null)); } @Test - public void testIllegalConstructorBadOff2() throws Exception { + 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 - public void testWriteTo() throws Exception { + void testWriteTo() throws Exception { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); try (final ByteArrayEntity entity = new ByteArrayEntity(bytes, null)) { @@ -118,7 +118,7 @@ public void testWriteTo() throws Exception { } @Test - public void testWriteToOffLen() throws Exception { + void testWriteToOffLen() throws Exception { final byte[] bytes = "Message content".getBytes(StandardCharsets.US_ASCII); final int off = 8; final int len = 7; diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestByteBufferEntity.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestByteBufferEntity.java index 2aca1b4d6b..15c1ef4274 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestByteBufferEntity.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestByteBufferEntity.java @@ -38,10 +38,10 @@ * Unit tests for {@link ByteBufferEntity}. * */ -public class TestByteBufferEntity { +class TestByteBufferEntity { @Test - public void testBasics() throws Exception { + void testBasics() throws Exception { final ByteBuffer bytes = ByteBuffer.wrap("Message content".getBytes(StandardCharsets.US_ASCII)); try (final ByteBufferEntity httpentity = new ByteBufferEntity(bytes, null)) { @@ -54,7 +54,7 @@ public void testBasics() throws Exception { @Test - public void testWriteTo() throws Exception { + void testWriteTo() throws Exception { final ByteBuffer bytes = ByteBuffer.wrap("Message content".getBytes(StandardCharsets.US_ASCII)); try (final ByteBufferEntity httpentity = new ByteBufferEntity(bytes, null)) { diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestEntityUtils.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestEntityUtils.java index fe60422b55..b306d6ac68 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestEntityUtils.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestEntityUtils.java @@ -51,26 +51,26 @@ * Unit tests for {@link EntityUtils}. * */ -public class TestEntityUtils { +class TestEntityUtils { @Test - public void testNullEntityToByteArray() throws Exception { + void testNullEntityToByteArray() { Assertions.assertThrows(NullPointerException.class, () -> EntityUtils.toByteArray(null)); } @Test - public void testMaxIntContentToByteArray() throws Exception { - final byte[] content = "Message content".getBytes(StandardCharsets.ISO_8859_1); + 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.ISO_8859_1)); + Integer.MAX_VALUE + 100L, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.US_ASCII)); Assertions.assertThrows(IllegalArgumentException.class, () -> EntityUtils.toByteArray(entity)); } @Test - public void testUnknownLengthContentToByteArray() throws Exception { - final byte[] bytes = "Message content".getBytes(StandardCharsets.ISO_8859_1); + 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); @@ -81,8 +81,8 @@ public void testUnknownLengthContentToByteArray() throws Exception { } @Test - public void testKnownLengthContentToByteArray() throws Exception { - final byte[] bytes = "Message content".getBytes(StandardCharsets.ISO_8859_1); + 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); @@ -93,33 +93,33 @@ public void testKnownLengthContentToByteArray() throws Exception { } @Test - public void testNullEntityToString() throws Exception { + void testNullEntityToString() { Assertions.assertThrows(NullPointerException.class, () -> EntityUtils.toString(null)); } @Test - public void testMaxIntContentToString() throws Exception { - final byte[] content = "Message content".getBytes(StandardCharsets.ISO_8859_1); + 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.ISO_8859_1)); + Integer.MAX_VALUE + 100L, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.US_ASCII)); Assertions.assertThrows(IllegalArgumentException.class, () -> - EntityUtils.toString(entity)); + EntityUtils.toString(entity, "US-ASCII")); } @Test - public void testUnknownLengthContentToString() throws Exception { - final byte[] bytes = "Message content".getBytes(StandardCharsets.ISO_8859_1); + 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, "ISO-8859-1"); + final String s = EntityUtils.toString(entity, "US-ASCII"); Assertions.assertEquals("Message content", s); } @Test - public void testKnownLengthContentToString() throws Exception { - final byte[] bytes = "Message content".getBytes(StandardCharsets.ISO_8859_1); + 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.ISO_8859_1)); - final String s = EntityUtils.toString(entity, StandardCharsets.ISO_8859_1); + ContentType.TEXT_PLAIN.withCharset(StandardCharsets.US_ASCII)); + final String s = EntityUtils.toString(entity, StandardCharsets.US_ASCII); Assertions.assertEquals("Message content", s); } @@ -143,15 +143,15 @@ private static String constructString(final int [] unicodeChars) { } @Test - public void testNoCharsetContentToString() throws Exception { + void testNoCharsetContentToString() { final String content = constructString(SWISS_GERMAN_HELLO); - final byte[] bytes = content.getBytes(StandardCharsets.ISO_8859_1); + final byte[] bytes = content.getBytes(StandardCharsets.UTF_8); final BasicHttpEntity entity = new BasicHttpEntity(new ByteArrayInputStream(bytes), ContentType.TEXT_PLAIN); - final String s = EntityUtils.toString(entity); + Assertions.assertDoesNotThrow(() -> EntityUtils.toString(entity)); } @Test - public void testDefaultCharsetContentToString() throws Exception { + 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), @@ -161,7 +161,7 @@ public void testDefaultCharsetContentToString() throws Exception { } @Test - public void testContentWithContentTypeToString() throws Exception { + 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), @@ -171,7 +171,7 @@ public void testContentWithContentTypeToString() throws Exception { } @Test - public void testContentWithInvalidContentTypeToString() throws Exception { + 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) { @@ -209,7 +209,7 @@ private static void assertNameValuePair ( } @Test - public void testParseEntity() throws Exception { + 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()); @@ -220,7 +220,7 @@ public void testParseEntity() throws Exception { } @Test - public void testParseUTF8Entity() throws Exception { + void testParseUTF8Entity() throws Exception { final String ru_hello = constructString(RUSSIAN_HELLO); final String ch_hello = constructString(SWISS_GERMAN_HELLO); final List parameters = new ArrayList<>(); @@ -240,8 +240,8 @@ public void testParseUTF8Entity() throws Exception { } @Test - public void testByteArrayMaxResultLength() throws IOException { - final byte[] allBytes = "Message content".getBytes(StandardCharsets.ISO_8859_1); + 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)); @@ -262,7 +262,7 @@ public void testByteArrayMaxResultLength() throws IOException { } @Test - public void testByteArrayMaxResultLengthWithNoContentLength() throws IOException { + void testByteArrayMaxResultLengthWithNoContentLength() throws IOException { final byte b = 'b'; final byte[] allBytes = new byte[5000]; Arrays.fill(allBytes, b); @@ -286,9 +286,9 @@ public void testByteArrayMaxResultLengthWithNoContentLength() throws IOException } @Test - public void testStringMaxResultLength() throws IOException, ParseException { + void testStringMaxResultLength() throws IOException, ParseException { final String allMessage = "Message content"; - final byte[] allBytes = allMessage.getBytes(StandardCharsets.ISO_8859_1); + 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); @@ -296,7 +296,7 @@ public void testStringMaxResultLength() throws IOException, ParseException { 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.ISO_8859_1, tc.getKey()); + final String string = EntityUtils.toString(entity, StandardCharsets.US_ASCII, tc.getKey()); final String expectedString = tc.getValue(); Assertions.assertNotNull(string); Assertions.assertEquals(expectedString, string); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestFileEntity.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestFileEntity.java index 4e67ee1969..dfe2198e2f 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestFileEntity.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestFileEntity.java @@ -40,18 +40,18 @@ * Unit tests for {@link FileEntity}. * */ -public class TestFileEntity { +class TestFileEntity { @Test - public void testBasics() throws Exception { + 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()); - final InputStream content = httpentity.getContent(); - Assertions.assertNotNull(content); - content.close(); + try (InputStream content = httpentity.getContent()) { + Assertions.assertNotNull(content); + } Assertions.assertTrue(httpentity.isRepeatable()); Assertions.assertFalse(httpentity.isStreaming()); Assertions.assertTrue(tmpfile.delete(), "Failed to delete " + tmpfile); @@ -59,21 +59,21 @@ public void testBasics() throws Exception { } @Test - public void testNullConstructor() throws Exception { + void testNullConstructor() { Assertions.assertThrows(NullPointerException.class, () -> new FileEntity(null, ContentType.TEXT_PLAIN)); } @Test - public void testWriteTo() throws Exception { + void testWriteTo() throws Exception { final File tmpfile = File.createTempFile("testfile", ".txt"); tmpfile.deleteOnExit(); - final FileOutputStream outStream = new FileOutputStream(tmpfile); - outStream.write(0); - outStream.write(1); - outStream.write(2); - outStream.write(3); - outStream.close(); + 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)) { diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestHttpEntityWrapper.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestHttpEntityWrapper.java index 1f3889d64a..1b80ad3fad 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestHttpEntityWrapper.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestHttpEntityWrapper.java @@ -38,10 +38,10 @@ * Unit tests for {@link HttpEntityWrapper}. * */ -public class TestHttpEntityWrapper { +class TestHttpEntityWrapper { @Test - public void testBasics() throws Exception { + void testBasics() throws Exception { final StringEntity entity = new StringEntity("Message content", ContentType.TEXT_PLAIN, "blah", false); try (final HttpEntityWrapper wrapped = new HttpEntityWrapper(entity)) { @@ -56,14 +56,14 @@ public void testBasics() throws Exception { } @Test - public void testIllegalConstructor() throws Exception { + void testIllegalConstructor() { Assertions.assertThrows(NullPointerException.class, () -> new HttpEntityWrapper(null)); } @Test - public void testWriteTo() throws Exception { + void testWriteTo() throws Exception { final String s = "Message content"; - final byte[] bytes = s.getBytes(StandardCharsets.ISO_8859_1); + final byte[] bytes = s.getBytes(StandardCharsets.US_ASCII); final StringEntity entity = new StringEntity(s); try (final HttpEntityWrapper wrapped = new HttpEntityWrapper(entity)) { @@ -90,12 +90,12 @@ public void testWriteTo() throws Exception { } @Test - public void testConsumeContent() throws Exception { + void testConsumeContent() throws Exception { final String s = "Message content"; final StringEntity entity = new StringEntity(s); final HttpEntityWrapper wrapped = new HttpEntityWrapper(entity); EntityUtils.consume(wrapped); - EntityUtils.consume(wrapped); + Assertions.assertDoesNotThrow(() -> EntityUtils.consume(wrapped)); } } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestInputStreamEntity.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestInputStreamEntity.java index e68603247b..6701575965 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestInputStreamEntity.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestInputStreamEntity.java @@ -40,11 +40,11 @@ * Unit tests for {@link InputStreamEntity}. * */ -public class TestInputStreamEntity { +class TestInputStreamEntity { @Test - public void testBasics() throws Exception { - final byte[] bytes = "Message content".getBytes(StandardCharsets.ISO_8859_1); + 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()); @@ -54,44 +54,44 @@ public void testBasics() throws Exception { } @Test - public void testNullConstructor() throws Exception { + void testNullConstructor() { Assertions.assertThrows(NullPointerException.class, () -> new InputStreamEntity(null, 0, null)); } @Test - public void testUnknownLengthConstructor() throws Exception { + void testUnknownLengthConstructor() throws Exception { try (final InputStreamEntity entity = new InputStreamEntity(EmptyInputStream.INSTANCE, null)) { Assertions.assertEquals(-1, entity.getContentLength()); } } @Test - public void testWriteTo() throws Exception { + void testWriteTo() throws Exception { final String message = "Message content"; - final byte[] bytes = message.getBytes(StandardCharsets.ISO_8859_1); + 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.ISO_8859_1))) { + 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.ISO_8859_1.name()); + final String s = new String(writtenBytes, StandardCharsets.US_ASCII.name()); Assertions.assertEquals(message, s); } } @Test - public void testWriteToPartialContent() throws Exception { + void testWriteToPartialContent() throws Exception { final String message = "Message content"; - final byte[] bytes = message.getBytes(StandardCharsets.ISO_8859_1); + 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.ISO_8859_1))) { + ContentType.TEXT_PLAIN.withCharset(StandardCharsets.US_ASCII))) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); entity.writeTo(out); @@ -99,17 +99,17 @@ public void testWriteToPartialContent() throws Exception { Assertions.assertNotNull(writtenBytes); Assertions.assertEquals(contentLength, writtenBytes.length); - final String s = new String(writtenBytes, StandardCharsets.ISO_8859_1.name()); + final String s = new String(writtenBytes, StandardCharsets.US_ASCII.name()); Assertions.assertEquals(message.substring(0, contentLength), s); } } @Test - public void testWriteToUnknownLength() throws Exception { + void testWriteToUnknownLength() throws Exception { final String message = "Message content"; - final byte[] bytes = message.getBytes(StandardCharsets.ISO_8859_1); + final byte[] bytes = message.getBytes(StandardCharsets.US_ASCII); final InputStreamEntity entity = new InputStreamEntity(new ByteArrayInputStream(bytes), - ContentType.TEXT_PLAIN.withCharset(StandardCharsets.ISO_8859_1)); + ContentType.TEXT_PLAIN.withCharset(StandardCharsets.US_ASCII)); final ByteArrayOutputStream out = new ByteArrayOutputStream(); entity.writeTo(out); @@ -117,12 +117,12 @@ public void testWriteToUnknownLength() throws Exception { Assertions.assertNotNull(writtenBytes); Assertions.assertEquals(bytes.length, writtenBytes.length); - final String s = new String(writtenBytes, StandardCharsets.ISO_8859_1.name()); + final String s = new String(writtenBytes, StandardCharsets.US_ASCII.name()); Assertions.assertEquals(message, s); } @Test - public void testWriteToNull() throws Exception { + void testWriteToNull() throws Exception { try (final InputStreamEntity entity = new InputStreamEntity(EmptyInputStream.INSTANCE, 0, null)) { Assertions.assertThrows(NullPointerException.class, () -> entity.writeTo(null)); }} diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestNullEntity.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestNullEntity.java index bcf7f0eee1..3a068c670b 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestNullEntity.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestNullEntity.java @@ -38,30 +38,30 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; -public class TestNullEntity { +class TestNullEntity { @Test - public void testLength() { + void testLength() { assertEquals(0, NullEntity.INSTANCE.getContentLength()); } @Test - public void testContentType() { + void testContentType() { assertNull(NullEntity.INSTANCE.getContentType()); } @Test - public void testContentEncoding() { + void testContentEncoding() { assertNull(NullEntity.INSTANCE.getContentEncoding()); } @Test - public void testTrailerNames() { + void testTrailerNames() { assertEquals(Collections.emptySet(), NullEntity.INSTANCE.getTrailerNames()); } @Test - public void testContentStream() throws IOException { + void testContentStream() throws IOException { try (InputStream content = NullEntity.INSTANCE.getContent()) { assertEquals(-1, content.read()); } @@ -72,19 +72,19 @@ public void testContentStream() throws IOException { } @Test - public void testWriteTo() throws IOException { + void testWriteTo() throws IOException { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); NullEntity.INSTANCE.writeTo(baos); assertEquals(0, baos.size()); } @Test - public void testIsStreaming() { + void testIsStreaming() { assertFalse(NullEntity.INSTANCE.isStreaming()); } @Test - public void testIsChunked() { + void testIsChunked() { assertFalse(NullEntity.INSTANCE.isChunked()); } } \ No newline at end of file diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestPathEntity.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestPathEntity.java index 8b2150f7a1..2facca2104 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestPathEntity.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestPathEntity.java @@ -40,10 +40,10 @@ /** * Unit tests for {@link PathEntity}. */ -public class TestPathEntity { +class TestPathEntity { @Test - public void testBasics() throws Exception { + 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(); @@ -60,12 +60,12 @@ public void testBasics() throws Exception { } @Test - public void testNullConstructor() throws Exception { + void testNullConstructor() { Assertions.assertThrows(NullPointerException.class, () -> new PathEntity(null, ContentType.TEXT_PLAIN)); } @Test - public void testWriteTo() throws Exception { + 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(); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestSerializableEntity.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestSerializableEntity.java index c55447266b..c9350bf148 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestSerializableEntity.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestSerializableEntity.java @@ -36,7 +36,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestSerializableEntity { +class TestSerializableEntity { public static class SerializableObject implements Serializable { @@ -50,7 +50,7 @@ public SerializableObject() {} } @Test - public void testBasics() throws Exception { + void testBasics() throws Exception { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ObjectOutputStream out = new ObjectOutputStream(baos); @@ -67,7 +67,7 @@ public void testBasics() throws Exception { } @Test - public void testWriteTo() throws Exception { + void testWriteTo() throws Exception { final Serializable serializableObj = new SerializableObject(); try (final SerializableEntity httpentity = new SerializableEntity(serializableObj, null)) { diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestStringEntity.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestStringEntity.java index 78bc88c793..f6e370a912 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestStringEntity.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/entity/TestStringEntity.java @@ -38,14 +38,14 @@ /** * Unit tests for {@link StringEntity}. */ -public class TestStringEntity { +class TestStringEntity { @Test - public void testBasics() throws Exception { + 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.ISO_8859_1); + final byte[] bytes = s.getBytes(StandardCharsets.US_ASCII); Assertions.assertEquals(bytes.length, httpentity.getContentLength()); Assertions.assertNotNull(httpentity.getContent()); Assertions.assertTrue(httpentity.isRepeatable()); @@ -54,19 +54,19 @@ public void testBasics() throws Exception { } @Test - public void testNullConstructor() throws Exception { + void testNullConstructor() { Assertions.assertThrows(NullPointerException.class, () -> new StringEntity(null)); } @Test - public void testDefaultContent() throws Exception { + 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=ISO-8859-1", httpentity.getContentType()); + Assertions.assertEquals("text/plain; charset=UTF-8", httpentity.getContentType()); } private static String constructString(final int [] unicodeChars) { @@ -84,7 +84,7 @@ private static String constructString(final int [] unicodeChars) { }; @Test - public void testNullCharset() throws Exception { + 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()); @@ -97,9 +97,9 @@ public void testNullCharset() throws Exception { } @Test - public void testWriteTo() throws Exception { + void testWriteTo() throws Exception { final String s = "Message content"; - final byte[] bytes = s.getBytes(StandardCharsets.ISO_8859_1); + final byte[] bytes = s.getBytes(StandardCharsets.UTF_8); try (final StringEntity httpentity = new StringEntity(s)) { ByteArrayOutputStream out = new ByteArrayOutputStream(); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicRequestBuilderTest.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicRequestBuilderTest.java index 33a1f9e940..729c6b89f7 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicRequestBuilderTest.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicRequestBuilderTest.java @@ -78,13 +78,13 @@ void constructor() throws UnknownHostException, URISyntaxException { } @Test - public void create() { + void create() { final ClassicHttpRequest classicHttpRequest = ClassicRequestBuilder.create(Method.HEAD.name()).build(); assertEquals(Method.HEAD.name(), classicHttpRequest.getMethod()); } @Test - public void get() throws UnknownHostException, URISyntaxException { + void get() throws UnknownHostException, URISyntaxException { final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.get(); assertEquals(Method.GET.name(), classicRequestBuilder.getMethod()); @@ -97,7 +97,7 @@ public void get() throws UnknownHostException, URISyntaxException { } @Test - public void head() throws UnknownHostException, URISyntaxException { + void head() throws UnknownHostException, URISyntaxException { final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.head(); assertEquals(Method.HEAD.name(), classicRequestBuilder.getMethod()); @@ -110,7 +110,7 @@ public void head() throws UnknownHostException, URISyntaxException { } @Test - public void patch() throws UnknownHostException, URISyntaxException { + void patch() throws UnknownHostException, URISyntaxException { final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.patch(); assertEquals(Method.PATCH.name(), classicRequestBuilder.getMethod()); @@ -123,7 +123,7 @@ public void patch() throws UnknownHostException, URISyntaxException { } @Test - public void post() throws UnknownHostException, URISyntaxException { + void post() throws UnknownHostException, URISyntaxException { final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.post(); assertEquals(Method.POST.name(), classicRequestBuilder.getMethod()); @@ -136,7 +136,7 @@ public void post() throws UnknownHostException, URISyntaxException { } @Test - public void put() throws UnknownHostException, URISyntaxException { + void put() throws UnknownHostException, URISyntaxException { final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.put(); assertEquals(Method.PUT.name(), classicRequestBuilder.getMethod()); @@ -149,7 +149,7 @@ public void put() throws UnknownHostException, URISyntaxException { } @Test - public void delete() throws UnknownHostException, URISyntaxException { + void delete() throws UnknownHostException, URISyntaxException { final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.delete(); assertEquals(Method.DELETE.name(), classicRequestBuilder.getMethod()); @@ -162,7 +162,7 @@ public void delete() throws UnknownHostException, URISyntaxException { } @Test - public void trace() throws UnknownHostException, URISyntaxException { + void trace() throws UnknownHostException, URISyntaxException { final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.trace(); assertEquals(Method.TRACE.name(), classicRequestBuilder.getMethod()); @@ -175,7 +175,7 @@ public void trace() throws UnknownHostException, URISyntaxException { } @Test - public void option() throws UnknownHostException, URISyntaxException { + void option() throws UnknownHostException, URISyntaxException { final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.options(); assertEquals(Method.OPTIONS.name(), classicRequestBuilder.getMethod()); @@ -188,7 +188,7 @@ public void option() throws UnknownHostException, URISyntaxException { } @Test - public void builder() { + void builder() { final Header header = new BasicHeader("header2", "blah"); final ClassicHttpRequest classicHttpRequest = ClassicRequestBuilder.get() .setVersion(HttpVersion.HTTP_1_1) @@ -228,7 +228,7 @@ public void builder() { @Test - public void builderTraceThrowsIllegalStateException() { + void builderTraceThrowsIllegalStateException() { Assertions.assertThrows(IllegalStateException.class, () -> ClassicRequestBuilder.trace() .setVersion(HttpVersion.HTTP_1_1) diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicResponseBuilderTest.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicResponseBuilderTest.java index 32a37775c7..5b8d2f0f59 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicResponseBuilderTest.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicResponseBuilderTest.java @@ -60,7 +60,7 @@ class ClassicResponseBuilderTest { ByteArrayOutputStream outStream; @BeforeEach - public void prepareMocks() throws IOException { + void prepareMocks() throws IOException { MockitoAnnotations.openMocks(this); conn = new DefaultBHttpServerConnection("http", Http1Config.DEFAULT, null, null, diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/HttpRequestWrapperTest.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/HttpRequestWrapperTest.java index a1b063656b..a47a7b7535 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/HttpRequestWrapperTest.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/HttpRequestWrapperTest.java @@ -37,10 +37,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class HttpRequestWrapperTest { +class HttpRequestWrapperTest { @Test - public void testRequestBasics() throws Exception { + void testRequestBasics() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, "/stuff"); final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request); @@ -53,7 +53,7 @@ public void testRequestBasics() throws Exception { } @Test - public void testDefaultRequestConstructors() { + void testDefaultRequestConstructors() { final HttpRequest request1 = new BasicHttpRequest("WHATEVER", "/"); final HttpRequestWrapper httpRequestWrapper = new HttpRequestWrapper(request1); Assertions.assertEquals("WHATEVER", httpRequestWrapper.getMethod()); @@ -70,7 +70,7 @@ public void testDefaultRequestConstructors() { @Test - public void testRequestWithRelativeURI() throws Exception { + 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()); @@ -80,7 +80,7 @@ public void testRequestWithRelativeURI() throws Exception { } @Test - public void testRequestWithAbsoluteURI() throws Exception { + 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()); @@ -93,7 +93,7 @@ public void testRequestWithAbsoluteURI() throws Exception { } @Test - public void testRequestWithAbsoluteURIAsPath() throws Exception { + 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()); @@ -104,7 +104,7 @@ public void testRequestWithAbsoluteURIAsPath() throws Exception { } @Test - public void testRequestWithNoPath() throws Exception { + 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()); @@ -115,7 +115,7 @@ public void testRequestWithNoPath() throws Exception { } @Test - public void testRequestWithUserInfo() throws Exception { + 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()); @@ -126,7 +126,7 @@ public void testRequestWithUserInfo() throws Exception { } @Test - public void testRequestWithAuthority() throws Exception { + 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()); @@ -140,7 +140,7 @@ public void testRequestWithAuthority() throws Exception { } @Test - public void testRequestWithAuthorityRelativePath() throws Exception { + 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()); @@ -151,7 +151,7 @@ public void testRequestWithAuthorityRelativePath() throws Exception { } @Test - public void testRequestHostWithReservedChars() throws Exception { + 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()); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/HttpResponseWrapperTest.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/HttpResponseWrapperTest.java index 8630b73317..445971bce7 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/HttpResponseWrapperTest.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/HttpResponseWrapperTest.java @@ -34,7 +34,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class HttpResponseWrapperTest { +class HttpResponseWrapperTest { @Test void testDefaultResponseConstructors() { @@ -57,7 +57,6 @@ void testSetResponseStatus() { final HttpResponse response1 = new BasicHttpResponse(200, "OK"); final HttpResponseWrapper httpResponseWrapper1 = new HttpResponseWrapper(response1); - Assertions.assertNotNull(httpResponseWrapper1.getCode()); Assertions.assertEquals(200, httpResponseWrapper1.getCode()); final HttpResponse response2 = new BasicHttpResponse(HttpStatus.SC_BAD_REQUEST, "Bad Request"); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeader.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeader.java index ca5222aae0..b7cfecc3dc 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeader.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeader.java @@ -33,22 +33,22 @@ /** * Tests for {@link BasicHeader}. */ -public class TestBasicHeader { +class TestBasicHeader { @Test - public void testConstructor() { + void testConstructor() { final Header param = new BasicHeader("name", "value"); Assertions.assertEquals("name", param.getName()); Assertions.assertEquals("value", param.getValue()); } @Test - public void testInvalidName() { + void testInvalidName() { Assertions.assertThrows(NullPointerException.class, () -> new BasicHeader(null, null)); } @Test - public void testToString() { + void testToString() { final Header param1 = new BasicHeader("name1", "value1"); Assertions.assertEquals("name1: value1", param1.toString()); final Header param2 = new BasicHeader("name1", null); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderElementIterator.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderElementIterator.java index 72dd92048d..6355610dc2 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderElementIterator.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderElementIterator.java @@ -38,10 +38,10 @@ * Tests for {@link BasicHeaderElementIterator}. * */ -public class TestBasicHeaderElementIterator { +class TestBasicHeaderElementIterator { @Test - public void testMultiHeader() { + void testMultiHeader() { final Header[] headers = new Header[]{ new BasicHeader("Name", "value0"), new BasicHeader("Name", "value1") @@ -66,7 +66,7 @@ public void testMultiHeader() { } @Test - public void testMultiHeaderSameLine() { + void testMultiHeaderSameLine() { final Header[] headers = new Header[]{ new BasicHeader("name", "value0,value1"), new BasicHeader("nAme", "cookie1=1,cookie2=2") @@ -89,7 +89,7 @@ public void testMultiHeaderSameLine() { } @Test - public void testFringeCases() { + void testFringeCases() { final Header[] headers = new Header[]{ new BasicHeader("Name", null), new BasicHeader("Name", " "), diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderIterator.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderIterator.java index 10e5eaffad..62cbc8c72f 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderIterator.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderIterator.java @@ -35,13 +35,13 @@ /** - * Tests for {@link java.util.Iterator} of {@link org.apache.hc.core5.http.Header}s. + * Tests for {@link Iterator} of {@link org.apache.hc.core5.http.Header}s. * */ -public class TestBasicHeaderIterator { +class TestBasicHeaderIterator { @Test - public void testAllSame() { + void testAllSame() { final Header[] headers = new Header[]{ new BasicHeader("Name", "value0"), new BasicHeader("nAme", "value1, value1.1"), @@ -76,7 +76,7 @@ public void testAllSame() { @Test - public void testFirstLastOneNone() { + void testFirstLastOneNone() { final Header[] headers = new Header[]{ new BasicHeader("match" , "value0"), new BasicHeader("mismatch", "value1, value1.1"), @@ -117,7 +117,7 @@ public void testFirstLastOneNone() { @Test - public void testInterspersed() { + void testInterspersed() { final Header[] headers = new Header[]{ new BasicHeader("yellow", "00"), new BasicHeader("maroon", "01"), @@ -215,7 +215,7 @@ public void testInterspersed() { @Test - public void testInvalid() { + void testInvalid() { Assertions.assertThrows(NullPointerException.class, () -> new BasicHeaderIterator(null, "whatever")); // this is not invalid final Iterator
hit = new BasicHeaderIterator(new Header[0], "whatever"); @@ -226,7 +226,7 @@ public void testInvalid() { } @Test - public void testRemaining() { + void testRemaining() { // to satisfy Clover and take coverage to 100% final Header[] headers = new Header[]{ diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderValueFormatter.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderValueFormatter.java index 6d18d88503..079ad8caa3 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderValueFormatter.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderValueFormatter.java @@ -38,17 +38,17 @@ * * */ -public class TestBasicHeaderValueFormatter { +class TestBasicHeaderValueFormatter { private BasicHeaderValueFormatter formatter; @BeforeEach - public void setup() { + void setup() { this.formatter = BasicHeaderValueFormatter.INSTANCE; } @Test - public void testNVPFormatting() throws Exception { + 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"); @@ -105,7 +105,7 @@ public void testNVPFormatting() throws Exception { } @Test - public void testParamsFormatting() throws Exception { + 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"); @@ -124,7 +124,7 @@ public void testParamsFormatting() throws Exception { } @Test - public void testHEFormatting() throws Exception { + 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"); @@ -140,15 +140,15 @@ public void testHEFormatting() throws Exception { } @Test - public void testElementsFormatting() throws Exception { + 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", new NameValuePair[] {param1}); - final HeaderElement element2 = new BasicHeaderElement("name2", "value2", new NameValuePair[] {param2}); - final HeaderElement element3 = new BasicHeaderElement("name3", "value3", new NameValuePair[] {param3}); - final HeaderElement element4 = new BasicHeaderElement("name4", "value4", new NameValuePair[] {param4}); + 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}; @@ -162,11 +162,11 @@ public void testElementsFormatting() throws Exception { @Test - public void testInvalidArguments() throws Exception { + 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", null); + final HeaderElement element = new BasicHeaderElement("name1", "value1", (NameValuePair[]) null); final HeaderElement[] elements = new HeaderElement[] {element}; Assertions.assertThrows(NullPointerException.class, () -> diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderValueParser.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderValueParser.java index c4cdd15820..0c0f672f81 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderValueParser.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicHeaderValueParser.java @@ -38,17 +38,17 @@ * * @version $Id$ */ -public class TestBasicHeaderValueParser { +class TestBasicHeaderValueParser { private BasicHeaderValueParser parser; @BeforeEach - public void setup() { + void setup() { this.parser = BasicHeaderValueParser.INSTANCE; } @Test - public void testParseHeaderElements() throws Exception { + 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); @@ -85,7 +85,7 @@ public void testParseHeaderElements() throws Exception { } @Test - public void testParseHEEscaped() { + void testParseHEEscaped() { final String headerValue = "test1 = \"\\\"stuff\\\"\", test2= \"\\\\\", test3 = \"stuff, stuff\""; final CharArrayBuffer buf = new CharArrayBuffer(64); @@ -102,7 +102,7 @@ public void testParseHEEscaped() { } @Test - public void testHEFringeCase1() throws Exception { + void testHEFringeCase1() { final String headerValue = "name1 = value1,"; final CharArrayBuffer buf = new CharArrayBuffer(64); buf.append(headerValue); @@ -112,7 +112,7 @@ public void testHEFringeCase1() throws Exception { } @Test - public void testHEFringeCase2() throws Exception { + void testHEFringeCase2() { final String headerValue = "name1 = value1, "; final CharArrayBuffer buf = new CharArrayBuffer(64); buf.append(headerValue); @@ -122,7 +122,7 @@ public void testHEFringeCase2() throws Exception { } @Test - public void testHEFringeCase3() throws Exception { + void testHEFringeCase3() { final String headerValue = ",, ,, ,"; final CharArrayBuffer buf = new CharArrayBuffer(64); buf.append(headerValue); @@ -132,7 +132,7 @@ public void testHEFringeCase3() throws Exception { } @Test - public void testNVParse() { + void testNVParse() { String s = "test"; CharArrayBuffer buffer = new CharArrayBuffer(64); @@ -153,8 +153,8 @@ public void testNVParse() { 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()); + Assertions.assertEquals(s.length() - 1, cursor.getPos()); + Assertions.assertFalse(cursor.atEnd()); s = "test ,12"; buffer = new CharArrayBuffer(16); @@ -164,7 +164,7 @@ public void testNVParse() { param = this.parser.parseNameValuePair(buffer, cursor); Assertions.assertEquals("test", param.getName()); Assertions.assertNull(param.getValue()); - Assertions.assertEquals(s.length() - 2, cursor.getPos()); + Assertions.assertEquals(s.length() - 3, cursor.getPos()); Assertions.assertFalse(cursor.atEnd()); s = "test=stuff"; @@ -197,7 +197,7 @@ public void testNVParse() { param = this.parser.parseNameValuePair(buffer, cursor); Assertions.assertEquals("test", param.getName()); Assertions.assertEquals("stuff", param.getValue()); - Assertions.assertEquals(s.length() - 4, cursor.getPos()); + Assertions.assertEquals(s.length() - 5, cursor.getPos()); Assertions.assertFalse(cursor.atEnd()); s = "test = \"stuff\""; @@ -247,7 +247,7 @@ public void testNVParse() { } @Test - public void testNVParseAll() { + void testNVParseAll() { String s = "test; test1 = stuff ; test2 = \"stuff; stuff\"; test3=\"stuff"; CharArrayBuffer buffer = new CharArrayBuffer(16); @@ -281,7 +281,7 @@ public void testNVParseAll() { Assertions.assertEquals("stuff; stuff", params[2].getValue()); Assertions.assertEquals("test3", params[3].getName()); Assertions.assertEquals("stuff", params[3].getValue()); - Assertions.assertEquals(s.length() - 3, cursor.getPos()); + Assertions.assertEquals(s.length() - 4, cursor.getPos()); Assertions.assertFalse(cursor.atEnd()); s = " "; @@ -293,7 +293,7 @@ public void testNVParseAll() { } @Test - public void testNVParseEscaped() { + void testNVParseEscaped() { final String headerValue = "test1 = \"\\\"stuff\\\"\"; test2= \"\\\\\"; test3 = \"stuff; stuff\""; final CharArrayBuffer buf = new CharArrayBuffer(64); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicLineFormatter.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicLineFormatter.java index d5d7008421..c7892ac912 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicLineFormatter.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicLineFormatter.java @@ -39,24 +39,24 @@ /** * Tests for {@link BasicLineFormatter}. */ -public class TestBasicLineFormatter { +class TestBasicLineFormatter { private BasicLineFormatter formatter; @BeforeEach - public void setup() { + void setup() { this.formatter = BasicLineFormatter.INSTANCE; } @Test - public void testHttpVersionFormatting() throws Exception { + void testHttpVersionFormatting() { final CharArrayBuffer buf = new CharArrayBuffer(64); this.formatter.formatProtocolVersion(buf, HttpVersion.HTTP_1_1); Assertions.assertEquals("HTTP/1.1", buf.toString()); } @Test - public void testRLFormatting() throws Exception { + 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); @@ -64,7 +64,7 @@ public void testRLFormatting() throws Exception { } @Test - public void testRLFormattingInvalidInput() throws Exception { + 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, () -> @@ -74,7 +74,7 @@ public void testRLFormattingInvalidInput() throws Exception { } @Test - public void testSLFormatting() throws Exception { + 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); @@ -89,7 +89,7 @@ public void testSLFormatting() throws Exception { } @Test - public void testSLFormattingInvalidInput() throws Exception { + 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, () -> @@ -99,7 +99,7 @@ public void testSLFormattingInvalidInput() throws Exception { } @Test - public void testHeaderFormatting() throws Exception { + void testHeaderFormatting() { final CharArrayBuffer buf = new CharArrayBuffer(64); final Header header1 = new BasicHeader("name", "value"); this.formatter.formatHeader(buf, header1); @@ -112,7 +112,7 @@ public void testHeaderFormatting() throws Exception { } @Test - public void testHeaderFormattingInvalidInput() throws Exception { + void testHeaderFormattingInvalidInput() { final CharArrayBuffer buf = new CharArrayBuffer(64); final Header header = new BasicHeader("name", "value"); Assertions.assertThrows(NullPointerException.class, () -> @@ -122,7 +122,7 @@ public void testHeaderFormattingInvalidInput() throws Exception { } @Test - public void testHeaderFormattingRequestSplitting() throws Exception { + void testHeaderFormattingRequestSplitting() { final CharArrayBuffer buf = new CharArrayBuffer(64); final Header header = new BasicHeader("Host", "apache.org\r\nOops: oops"); formatter.formatHeader(buf, header); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicLineParser.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicLineParser.java index 82a0592a31..30f7b39dd4 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicLineParser.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicLineParser.java @@ -31,6 +31,7 @@ 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; @@ -40,17 +41,17 @@ * Tests for {@link BasicLineParser}. * */ -public class TestBasicLineParser { +class TestBasicLineParser { private BasicLineParser parser; @BeforeEach - public void setup() { + void setup() { this.parser = BasicLineParser.INSTANCE; } @Test - public void testRLParse() throws Exception { + void testRLParse() throws Exception { final CharArrayBuffer buf = new CharArrayBuffer(64); //typical request line buf.clear(); @@ -80,7 +81,7 @@ public void testRLParse() throws Exception { } @Test - public void testRLParseFailure() throws Exception { + void testRLParseFailure() { final CharArrayBuffer buf = new CharArrayBuffer(64); buf.clear(); buf.append(" "); @@ -100,7 +101,7 @@ public void testRLParseFailure() throws Exception { } @Test - public void testSLParse() throws Exception { + void testSLParse() throws Exception { final CharArrayBuffer buf = new CharArrayBuffer(64); //typical status line buf.clear(); @@ -169,7 +170,7 @@ public void testSLParse() throws Exception { } @Test - public void testSLParseFailure() throws Exception { + void testSLParseFailure() { final CharArrayBuffer buf = new CharArrayBuffer(64); buf.clear(); buf.append("xxx 200 OK"); @@ -186,15 +187,21 @@ public void testSLParseFailure() throws Exception { 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 - public void testHttpVersionParsing() throws Exception { + void testHttpVersionParsing() throws Exception { final CharArrayBuffer buffer = new CharArrayBuffer(16); buffer.append("HTTP/1.1"); - ParserCursor cursor = new ParserCursor(0, buffer.length()); + final ParserCursor cursor = new ParserCursor(0, buffer.length()); - HttpVersion version = (HttpVersion) parser.parseProtocolVersion(buffer, cursor); + 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"); @@ -204,20 +211,20 @@ public void testHttpVersionParsing() throws Exception { buffer.clear(); buffer.append("HTTP/1.123 123"); - cursor = new ParserCursor(0, buffer.length()); + final ParserCursor cursor2 = new ParserCursor(0, buffer.length()); - version = (HttpVersion) parser.parseProtocolVersion(buffer, cursor); - Assertions.assertEquals( "HTTP", version.getProtocol(), "HTTP protocol name"); - Assertions.assertEquals( 1, version.getMajor(), "HTTP major version number"); - Assertions.assertEquals(123, version.getMinor(), "HTTP minor version number"); - Assertions.assertEquals("HTTP/1.123", version.toString(), "HTTP version number"); - Assertions.assertEquals(' ', buffer.charAt(cursor.getPos())); - Assertions.assertEquals(buffer.length() - 4, cursor.getPos()); - Assertions.assertFalse(cursor.atEnd()); + 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 - public void testInvalidHttpVersionParsing() throws Exception { + void testInvalidHttpVersionParsing() { final CharArrayBuffer buffer = new CharArrayBuffer(16); buffer.clear(); buffer.append(" "); @@ -245,11 +252,6 @@ public void testInvalidHttpVersionParsing() throws Exception { Assertions.assertThrows(ParseException.class, () -> parser.parseProtocolVersion(buffer, cursor5)); buffer.clear(); - buffer.append("HTTP/1234"); - final ParserCursor cursor6 = new ParserCursor(0, buffer.length()); - Assertions.assertThrows(ParseException.class, () -> - parser.parseProtocolVersion(buffer, cursor6)); - buffer.clear(); buffer.append("HTTP/1."); final ParserCursor cursor7 = new ParserCursor(0, buffer.length()); Assertions.assertThrows(ParseException.class, () -> @@ -267,7 +269,7 @@ public void testInvalidHttpVersionParsing() throws Exception { } @Test - public void testHeaderParse() throws Exception { + void testHeaderParse() throws Exception { final CharArrayBuffer buf = new CharArrayBuffer(64); //typical request line buf.clear(); @@ -285,7 +287,7 @@ public void testHeaderParse() throws Exception { } @Test - public void testInvalidHeaderParsing() throws Exception { + void testInvalidHeaderParsing() { final CharArrayBuffer buffer = new CharArrayBuffer(16); buffer.clear(); buffer.append(""); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicMessages.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicMessages.java index 54a009584b..d31bb63beb 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicMessages.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicMessages.java @@ -43,10 +43,10 @@ * Unit tests for {@link org.apache.hc.core5.http.HttpMessage}. * */ -public class TestBasicMessages { +class TestBasicMessages { @Test - public void testDefaultResponseConstructors() { + void testDefaultResponseConstructors() { final HttpResponse response1 = new BasicHttpResponse(HttpStatus.SC_BAD_REQUEST, "Bad Request"); Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, response1.getCode()); @@ -56,9 +56,8 @@ public void testDefaultResponseConstructors() { } @Test - public void testSetResponseStatus() { + void testSetResponseStatus() { final HttpResponse response1 = new BasicHttpResponse(200, "OK"); - Assertions.assertNotNull(response1.getCode()); Assertions.assertEquals(200, response1.getCode()); final HttpResponse response2 = new BasicHttpResponse(HttpStatus.SC_BAD_REQUEST, "Bad Request"); @@ -73,7 +72,7 @@ public void testSetResponseStatus() { } @Test - public void testDefaultRequestConstructors() { + void testDefaultRequestConstructors() { final HttpRequest request1 = new BasicHttpRequest("WHATEVER", "/"); Assertions.assertEquals("WHATEVER", request1.getMethod()); Assertions.assertEquals("/", request1.getPath()); @@ -87,14 +86,14 @@ public void testDefaultRequestConstructors() { } @Test - public void testResponseBasics() { + void testResponseBasics() { final BasicHttpResponse response = new BasicHttpResponse(200, "OK"); Assertions.assertEquals(200, response.getCode()); Assertions.assertEquals("OK", response.getReasonPhrase()); } @Test - public void testResponseStatusLineMutation() { + void testResponseStatusLineMutation() { final BasicHttpResponse response = new BasicHttpResponse(200, "OK"); Assertions.assertEquals(200, response.getCode()); Assertions.assertEquals("OK", response.getReasonPhrase()); @@ -107,14 +106,14 @@ public void testResponseStatusLineMutation() { } @Test - public void testResponseInvalidStatusCode() { + 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 - public void testRequestBasics() throws Exception { + void testRequestBasics() throws Exception { final HttpRequest request = new BasicHttpRequest(Method.GET, "/stuff"); Assertions.assertEquals(Method.GET.name(), request.getMethod()); Assertions.assertEquals("/stuff", request.getPath()); @@ -123,7 +122,7 @@ public void testRequestBasics() throws Exception { } @Test - public void testRequestWithRelativeURI() throws Exception { + 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()); @@ -132,7 +131,7 @@ public void testRequestWithRelativeURI() throws Exception { } @Test - public void testRequestWithAbsoluteURI() throws Exception { + 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()); @@ -142,7 +141,7 @@ public void testRequestWithAbsoluteURI() throws Exception { } @Test - public void testRequestWithAbsoluteURIAsPath() throws Exception { + 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()); @@ -152,7 +151,7 @@ public void testRequestWithAbsoluteURIAsPath() throws Exception { } @Test - public void testRequestWithNoPath() throws Exception { + 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()); @@ -162,7 +161,7 @@ public void testRequestWithNoPath() throws Exception { } @Test - public void testRequestWithUserInfo() throws Exception { + 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()); @@ -172,7 +171,7 @@ public void testRequestWithUserInfo() throws Exception { } @Test - public void testRequestWithAuthority() throws Exception { + 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()); @@ -181,7 +180,7 @@ public void testRequestWithAuthority() throws Exception { } @Test - public void testRequestWithAuthorityRelativePath() throws Exception { + 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()); @@ -190,7 +189,7 @@ public void testRequestWithAuthorityRelativePath() throws Exception { } @Test - public void testRequestHostWithReservedChars() throws Exception { + 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()); @@ -199,13 +198,7 @@ public void testRequestHostWithReservedChars() throws Exception { } @Test - public void testRequestPathWithMultipleLeadingSlashes() throws Exception { - Assertions.assertThrows(IllegalArgumentException.class, () -> - new BasicHttpRequest(Method.GET, URI.create("http://host//stuff"))); - } - - @Test - public void testRequestAbsoluteRequestUri() throws Exception { + void testRequestAbsoluteRequestUri() { final BasicHttpRequest request = new BasicHttpRequest(Method.GET, new HttpHost("http", "somehost", -1), "stuff"); Assertions.assertEquals("stuff", request.getRequestUri()); request.setAbsoluteRequestUri(true); @@ -213,7 +206,7 @@ public void testRequestAbsoluteRequestUri() throws Exception { } @Test - public void testModifyingExistingRequest() throws Exception { + void testModifyingExistingRequest() throws Exception { final URI uri = URI.create("https://example.org"); final HttpRequest request = new BasicHttpRequest(Method.GET, uri); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicStatusLine.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicStatusLine.java index caa853898c..d647a405be 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicStatusLine.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicStatusLine.java @@ -34,10 +34,10 @@ /** * Unit tests for {@link org.apache.hc.core5.http.message.StatusLine}. */ -public class TestBasicStatusLine { +class TestBasicStatusLine { @Test - public void testGetStatusClass() { + void testGetStatusClass() { StatusLine statusLine = new StatusLine(new BasicHttpResponse(100, "Continue")); Assertions.assertEquals(StatusClass.INFORMATIONAL, statusLine.getStatusClass()); @@ -58,7 +58,7 @@ public void testGetStatusClass() { } @Test - public void testGetStatusShorthand() { + void testGetStatusShorthand() { StatusLine statusLine = new StatusLine(new BasicHttpResponse(100, "Continue")); Assertions.assertTrue(statusLine.isInformational()); Assertions.assertFalse(statusLine.isSuccessful()); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicTokenIterator.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicTokenIterator.java index e41f0252b6..bda1042439 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicTokenIterator.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBasicTokenIterator.java @@ -39,10 +39,10 @@ * Tests for {@link BasicTokenIterator}. * */ -public class TestBasicTokenIterator { +class TestBasicTokenIterator { @Test - public void testSingleHeader() { + void testSingleHeader() { Header[] headers = new Header[]{ new BasicHeader("Name", "token0,token1, token2 , token3") }; @@ -50,13 +50,13 @@ public void testSingleHeader() { Iterator ti = new BasicTokenIterator(hit); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token0", "token0", ti.next()); + Assertions.assertEquals("token0", ti.next()); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token1", "token1", ti.next()); + Assertions.assertEquals("token1", ti.next()); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token2", "token2", ti.next()); + Assertions.assertEquals("token2", ti.next()); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token3", "token3", ti.next()); + Assertions.assertEquals("token3", ti.next()); Assertions.assertFalse(ti.hasNext()); @@ -67,13 +67,13 @@ public void testSingleHeader() { ti = new BasicTokenIterator(hit); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token0", "token0", ti.next()); + Assertions.assertEquals("token0", ti.next()); Assertions.assertFalse(ti.hasNext()); } @Test - public void testMultiHeader() { + void testMultiHeader() { final Header[] headers = new Header[]{ new BasicHeader("Name", "token0,token1"), new BasicHeader("Name", ""), @@ -87,21 +87,21 @@ public void testMultiHeader() { final Iterator ti = new BasicTokenIterator(hit); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token0", "token0", ti.next()); + Assertions.assertEquals("token0", ti.next()); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token1", "token1", ti.next()); + Assertions.assertEquals("token1", ti.next()); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token2", "token2", ti.next()); + Assertions.assertEquals("token2", ti.next()); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token3", "token3", ti.next()); + Assertions.assertEquals("token3", ti.next()); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token4", "token4", ti.next()); + Assertions.assertEquals("token4", ti.next()); Assertions.assertFalse(ti.hasNext()); } @Test - public void testEmpty() { + void testEmpty() { final Header[] headers = new Header[]{ new BasicHeader("Name", " "), new BasicHeader("Name", ""), @@ -122,7 +122,7 @@ public void testEmpty() { @Test - public void testValueStart() { + void testValueStart() { final Header[] headers = new Header[]{ new BasicHeader("Name", "token0"), new BasicHeader("Name", " token1"), @@ -135,23 +135,23 @@ public void testValueStart() { final Iterator ti = new BasicTokenIterator(hit); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token0", "token0", ti.next()); + Assertions.assertEquals("token0", ti.next()); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token1", "token1", ti.next()); + Assertions.assertEquals("token1", ti.next()); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token2", "token2", ti.next()); + Assertions.assertEquals("token2", ti.next()); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token3", "token3", ti.next()); + Assertions.assertEquals("token3", ti.next()); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token4", "token4", ti.next()); + Assertions.assertEquals("token4", ti.next()); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token5", "token5", ti.next()); + Assertions.assertEquals("token5", ti.next()); Assertions.assertFalse(ti.hasNext()); } @Test - public void testValueEnd() { + void testValueEnd() { final Header[] headers = new Header[]{ new BasicHeader("Name", "token0"), new BasicHeader("Name", "token1 "), @@ -164,22 +164,22 @@ public void testValueEnd() { final Iterator ti = new BasicTokenIterator(hit); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token0", "token0", ti.next()); + Assertions.assertEquals("token0", ti.next()); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token1", "token1", ti.next()); + Assertions.assertEquals("token1", ti.next()); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token2", "token2", ti.next()); + Assertions.assertEquals("token2", ti.next()); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token3", "token3", ti.next()); + Assertions.assertEquals("token3", ti.next()); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token4", "token4", ti.next()); + Assertions.assertEquals("token4", ti.next()); Assertions.assertTrue(ti.hasNext()); - Assertions.assertEquals("token5", "token5", ti.next()); + Assertions.assertEquals("token5", ti.next()); Assertions.assertFalse(ti.hasNext()); } @Test - public void testWrongPublic() { + void testWrongPublic() { Assertions.assertThrows(NullPointerException.class, () -> new BasicTokenIterator(null)); final Header[] headers = new Header[]{ diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBufferedHeader.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBufferedHeader.java index 2fd8ccbe73..99409f4b07 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBufferedHeader.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestBufferedHeader.java @@ -41,10 +41,10 @@ * Unit tests for {@link BufferedHeader}. * */ -public class TestBufferedHeader { +class TestBufferedHeader { @Test - public void testBasicConstructor() throws Exception { + void testBasicConstructor() throws Exception { final CharArrayBuffer buf = new CharArrayBuffer(32); buf.append("name: value"); final BufferedHeader header = new BufferedHeader(buf, false); @@ -55,14 +55,14 @@ public void testBasicConstructor() throws Exception { } @Test - public void testSerialization() throws Exception { + 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(); - final ObjectOutputStream outStream = new ObjectOutputStream(outbuffer); - outStream.writeObject(orig); - outStream.close(); + 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); @@ -72,7 +72,7 @@ public void testSerialization() throws Exception { } @Test - public void testInvalidHeaderParsing() throws Exception { + void testInvalidHeaderParsing() { final CharArrayBuffer buf = new CharArrayBuffer(16); buf.clear(); buf.append(""); @@ -97,5 +97,27 @@ public void testInvalidHeaderParsing() throws Exception { 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()); + } + } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestHeader.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestHeader.java index f44d363ca4..f7323d098a 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestHeader.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestHeader.java @@ -39,29 +39,29 @@ /** * Unit tests for {@link Header}. */ -public class TestHeader { +class TestHeader { @Test - public void testBasicConstructor() { + void testBasicConstructor() { final Header header = new BasicHeader("name", "value"); Assertions.assertEquals("name", header.getName()); Assertions.assertEquals("value", header.getValue()); } @Test - public void testBasicConstructorNullValue() { + void testBasicConstructorNullValue() { final Header header = new BasicHeader("name", null); Assertions.assertEquals("name", header.getName()); Assertions.assertNull(header.getValue()); } @Test - public void testInvalidName() { + void testInvalidName() { Assertions.assertThrows(NullPointerException.class, () -> new BasicHeader(null, null)); } @Test - public void testToString() { + void testToString() { final Header header1 = new BasicHeader("name1", "value1"); Assertions.assertEquals("name1: value1", header1.toString()); final Header header2 = new BasicHeader("name2", null); @@ -69,12 +69,12 @@ public void testToString() { } @Test - public void testSerialization() throws Exception { + void testSerialization() throws Exception { final BasicHeader orig = new BasicHeader("name1", "value1"); final ByteArrayOutputStream outbuffer = new ByteArrayOutputStream(); - final ObjectOutputStream outStream = new ObjectOutputStream(outbuffer); - outStream.writeObject(orig); - outStream.close(); + 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); @@ -85,7 +85,7 @@ public void testSerialization() throws Exception { @Test - public void testClone() throws Exception { + void testClone() throws Exception { final BasicHeader orig = new BasicHeader("name1", "value1"); final BasicHeader clone = orig.clone(); Assertions.assertEquals(orig.getName(), clone.getName()); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestHeaderElement.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestHeaderElement.java index 705fbf5030..d7cc507178 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestHeaderElement.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestHeaderElement.java @@ -36,15 +36,13 @@ /** * Simple tests for {@link HeaderElement}. */ -public class TestHeaderElement { +class TestHeaderElement { @Test - public void testConstructor3() throws Exception { + void testConstructor3() { final HeaderElement element = new BasicHeaderElement("name", "value", - new NameValuePair[] { - new BasicNameValuePair("param1", "value1"), - new BasicNameValuePair("param2", "value2") - } ); + new BasicNameValuePair("param1", "value1"), + new BasicNameValuePair("param2", "value2")); Assertions.assertEquals("name", element.getName()); Assertions.assertEquals("value", element.getValue()); Assertions.assertEquals(2, element.getParameters().length); @@ -53,7 +51,7 @@ public void testConstructor3() throws Exception { } @Test - public void testConstructor2() throws Exception { + void testConstructor2() { final HeaderElement element = new BasicHeaderElement("name", "value"); Assertions.assertEquals("name", element.getName()); Assertions.assertEquals("value", element.getValue()); @@ -62,12 +60,12 @@ public void testConstructor2() throws Exception { @Test - public void testInvalidName() { - Assertions.assertThrows(NullPointerException.class, () -> new BasicHeaderElement(null, null, null)); + void testInvalidName() { + Assertions.assertThrows(NullPointerException.class, () -> new BasicHeaderElement(null, null, (NameValuePair[]) null)); } @Test - public void testParamByName() throws Exception { + void testParamByName() { final String s = "name = value; param1 = value1; param2 = value2"; final CharArrayBuffer buf = new CharArrayBuffer(64); buf.append(s); @@ -80,12 +78,10 @@ public void testParamByName() throws Exception { } @Test - public void testToString() { + void testToString() { final BasicHeaderElement element = new BasicHeaderElement("name", "value", - new NameValuePair[] { - new BasicNameValuePair("param1", "value1"), - new BasicNameValuePair("param2", "value2") - } ); + new BasicNameValuePair("param1", "value1"), + new BasicNameValuePair("param2", "value2")); Assertions.assertEquals("name=value; param1=value1; param2=value2", element.toString()); } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestHeaderGroup.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestHeaderGroup.java index e251db81d2..9c1b8f83ec 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestHeaderGroup.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestHeaderGroup.java @@ -41,17 +41,17 @@ * Unit tests for {@link HeaderGroup}. * */ -public class TestHeaderGroup { +class TestHeaderGroup { @Test - public void testConstructor() { + void testConstructor() { final HeaderGroup headergroup = new HeaderGroup(); Assertions.assertNotNull(headergroup.getHeaders()); Assertions.assertEquals(0, headergroup.getHeaders().length); } @Test - public void testClear() { + void testClear() { final HeaderGroup headergroup = new HeaderGroup(); headergroup.addHeader(new BasicHeader("name", "value")); Assertions.assertEquals(1, headergroup.getHeaders().length); @@ -60,7 +60,7 @@ public void testClear() { } @Test - public void testAddRemoveHeader() { + void testAddRemoveHeader() { final HeaderGroup headerGroup = new HeaderGroup(); final Header header = new BasicHeader("name", "value"); headerGroup.addHeader(header); @@ -72,7 +72,7 @@ public void testAddRemoveHeader() { } @Test - public void testAddRemoveHeaders() { + void testAddRemoveHeaders() { final HeaderGroup headergroup = new HeaderGroup(); final Header header = new BasicHeader("name", "value"); headergroup.addHeader(header); @@ -85,7 +85,7 @@ public void testAddRemoveHeaders() { } @Test - public void testAddRemoveHeaderWithDifferentButEqualHeaders() { + void testAddRemoveHeaderWithDifferentButEqualHeaders() { final HeaderGroup headergroup = new HeaderGroup(); final Header header = new BasicHeader("name", "value"); final Header header2 = new BasicHeader("name", "value"); @@ -96,7 +96,7 @@ public void testAddRemoveHeaderWithDifferentButEqualHeaders() { } @Test - public void testUpdateHeader() { + void testUpdateHeader() { final HeaderGroup headergroup = new HeaderGroup(); final Header header1 = new BasicHeader("name1", "value1"); final Header header2 = new BasicHeader("name2", "value2"); @@ -112,7 +112,7 @@ public void testUpdateHeader() { } @Test - public void testSetHeaders() { + void testSetHeaders() { final HeaderGroup headergroup = new HeaderGroup(); final Header header1 = new BasicHeader("name1", "value1"); final Header header2 = new BasicHeader("name2", "value2"); @@ -131,7 +131,7 @@ public void testSetHeaders() { } @Test - public void testFirstLastHeaders() { + void testFirstLastHeaders() { final HeaderGroup headergroup = new HeaderGroup(); final Header header1 = new BasicHeader("name", "value1"); final Header header2 = new BasicHeader("name", "value2"); @@ -146,7 +146,7 @@ public void testFirstLastHeaders() { } @Test - public void testCondensedHeader() { + void testCondensedHeader() { final HeaderGroup headergroup = new HeaderGroup(); Assertions.assertNull(headergroup.getCondensedHeader("name")); @@ -162,7 +162,7 @@ public void testCondensedHeader() { } @Test - public void testIterator() { + void testIterator() { final HeaderGroup headergroup = new HeaderGroup(); final Iterator
i = headergroup.headerIterator(); Assertions.assertNotNull(i); @@ -170,7 +170,7 @@ public void testIterator() { } @Test - public void testHeaderRemove() { + void testHeaderRemove() { final HeaderGroup headergroup = new HeaderGroup(); final Header header1 = new BasicHeader("name", "value1"); final Header header2 = new BasicHeader("name", "value2"); @@ -192,16 +192,16 @@ public void testHeaderRemove() { } @Test - public void testSerialization() throws Exception { + 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(); - final ObjectOutputStream outStream = new ObjectOutputStream(outbuffer); - outStream.writeObject(orig); - outStream.close(); + 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); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestMessageSupport.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestMessageSupport.java index 613fb7df5a..75ee660088 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestMessageSupport.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestMessageSupport.java @@ -28,20 +28,29 @@ 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.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; -public class TestMessageSupport { +class TestMessageSupport { private static Set makeSet(final String... tokens) { if (tokens == null) { @@ -53,47 +62,73 @@ private static Set makeSet(final String... tokens) { } @Test - public void testTokenSetFormatting() throws Exception { - final Header header = MessageSupport.format(HttpHeaders.TRAILER, makeSet("z", "b", "a")); + void testTokenSetFormatting() { + final Header header = MessageSupport.header(HttpHeaders.TRAILER, makeSet("z", "b", "a")); Assertions.assertNotNull(header); Assertions.assertEquals("z, b, a", header.getValue()); } @Test - public void testTokenSetFormattingSameName() throws Exception { - final Header header = MessageSupport.format(HttpHeaders.TRAILER, makeSet("a", "a", "a")); + 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 testTokenSetFormattingSameName() { + final Header header = MessageSupport.header(HttpHeaders.TRAILER, makeSet("a", "a", "a")); Assertions.assertNotNull(header); Assertions.assertEquals("a", header.getValue()); } @Test - public void testTokensFormattingSameName() throws Exception { - final Header header = MessageSupport.format(HttpHeaders.TRAILER, "a", "a", "a"); + void testTokenListFormattingSameName() { + final Header header = MessageSupport.header(HttpHeaders.TRAILER, "a", "a", "a"); Assertions.assertNotNull(header); Assertions.assertEquals("a, a, a", header.getValue()); } @Test - public void testTrailerNoTrailers() throws Exception { - final Header header = MessageSupport.format(HttpHeaders.TRAILER); - Assertions.assertNull(header); + 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 - public void testParseTokens() throws Exception { + 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 - public void testParseTokenHeader() throws Exception { + void testParseTokenHeader() { final Header header = new BasicHeader(HttpHeaders.TRAILER, "a, b, c, c"); Assertions.assertEquals(makeSet("a", "b", "c"), MessageSupport.parseTokens(header)); } @Test - public void testParseTokenBufferedHeader() throws Exception { + void testParseTokenBuffer() { final CharArrayBuffer buf = new CharArrayBuffer(128); buf.append("stuff: a, b, c, c"); final Header header = BufferedHeader.create(buf); @@ -101,7 +136,193 @@ public void testParseTokenBufferedHeader() throws Exception { } @Test - public void testAddContentHeaders() throws Exception { + 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); @@ -118,7 +339,7 @@ public void testAddContentHeaders() throws Exception { } @Test - public void testContentHeadersAlreadyPresent() throws Exception { + 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); @@ -137,4 +358,30 @@ public void testContentHeadersAlreadyPresent() throws Exception { 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")); + } + } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestNameValuePair.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestNameValuePair.java index f04d34dd58..8d05c5396d 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestNameValuePair.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestNameValuePair.java @@ -35,22 +35,22 @@ * Unit tests for {@link NameValuePair}. * */ -public class TestNameValuePair { +class TestNameValuePair { @Test - public void testConstructor() { + void testConstructor() { final NameValuePair param = new BasicNameValuePair("name", "value"); Assertions.assertEquals("name", param.getName()); Assertions.assertEquals("value", param.getValue()); } @Test - public void testInvalidName() { + void testInvalidName() { Assertions.assertThrows(NullPointerException.class, () -> new BasicNameValuePair(null, null)); } @Test - public void testToString() { + void testToString() { final NameValuePair param1 = new BasicNameValuePair("name1", "value1"); Assertions.assertEquals("name1=value1", param1.toString()); final NameValuePair param2 = new BasicNameValuePair("name1", null); @@ -58,21 +58,21 @@ public void testToString() { } @Test - public void testNullNotEqual() throws Exception { + void testNullNotEqual() { final NameValuePair NameValuePair = new BasicNameValuePair("name", "value"); Assertions.assertNotEquals(null, NameValuePair); } @Test - public void testObjectNotEqual() throws Exception { + void testObjectNotEqual() { final NameValuePair NameValuePair = new BasicNameValuePair("name", "value"); Assertions.assertNotEquals(NameValuePair, new Object()); } @Test - public void testNameEquals() throws Exception { + void testNameEquals() { final NameValuePair NameValuePair = new BasicNameValuePair("name", "value"); final NameValuePair NameValuePair2 = new BasicNameValuePair("NAME", "value"); @@ -87,7 +87,7 @@ public void testNameEquals() throws Exception { } @Test - public void testValueEquals() throws Exception { + void testValueEquals() { final NameValuePair NameValuePair = new BasicNameValuePair("name", "value"); final NameValuePair NameValuePair2 = new BasicNameValuePair("name", "value"); @@ -96,7 +96,7 @@ public void testValueEquals() throws Exception { } @Test - public void testNameNotEqual() throws Exception { + void testNameNotEqual() { final NameValuePair NameValuePair = new BasicNameValuePair("name", "value"); final NameValuePair NameValuePair2 = new BasicNameValuePair("name2", "value"); @@ -104,7 +104,7 @@ public void testNameNotEqual() throws Exception { } @Test - public void testValueNotEqual() throws Exception { + void testValueNotEqual() { final NameValuePair NameValuePair = new BasicNameValuePair("name", "value"); final NameValuePair NameValuePair2 = new BasicNameValuePair("name", "value2"); @@ -122,7 +122,7 @@ public void testValueNotEqual() throws Exception { } @Test - public void testNullValuesEquals() throws Exception { + void testNullValuesEquals() { final NameValuePair NameValuePair = new BasicNameValuePair("name", null); final NameValuePair NameValuePair2 = new BasicNameValuePair("name", null); @@ -131,7 +131,7 @@ public void testNullValuesEquals() throws Exception { } @Test - public void testNullValueNotEqual() throws Exception { + void testNullValueNotEqual() { final NameValuePair NameValuePair = new BasicNameValuePair("name", null); final NameValuePair NameValuePair2 = new BasicNameValuePair("name", "value"); @@ -139,7 +139,7 @@ public void testNullValueNotEqual() throws Exception { } @Test - public void testNullValue2NotEqual() throws Exception { + void testNullValue2NotEqual() { final NameValuePair NameValuePair = new BasicNameValuePair("name", "value"); final NameValuePair NameValuePair2 = new BasicNameValuePair("name", null); @@ -147,7 +147,7 @@ public void testNullValue2NotEqual() throws Exception { } @Test - public void testEquals() throws Exception { + void testEquals() { final NameValuePair NameValuePair = new BasicNameValuePair("name", "value"); final NameValuePair NameValuePair2 = new BasicNameValuePair("name", "value"); @@ -160,7 +160,7 @@ public void testEquals() throws Exception { } @Test - public void testHashCode() throws Exception { + void testHashCode() { final NameValuePair NameValuePair = new BasicNameValuePair("name", null); final NameValuePair NameValuePair2 = new BasicNameValuePair("name2", null); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractBinAsyncEntityConsumer.java b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractBinAsyncEntityConsumer.java index bbc06f3b75..f20e95232b 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractBinAsyncEntityConsumer.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractBinAsyncEntityConsumer.java @@ -29,9 +29,8 @@ import java.io.IOException; import java.nio.ByteBuffer; -import java.util.concurrent.atomic.AtomicLong; -import org.apache.hc.core5.concurrent.FutureCallback; +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; @@ -40,7 +39,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestAbstractBinAsyncEntityConsumer { +class TestAbstractBinAsyncEntityConsumer { static private class ByteArrayAsyncEntityConsumer extends AbstractBinAsyncEntityConsumer { @@ -77,29 +76,10 @@ public void releaseResources() { } @Test - public void testConsumeData() throws Exception { - + void testConsumeData() throws Exception { final AsyncEntityConsumer consumer = new ByteArrayAsyncEntityConsumer(); - - final AtomicLong count = new AtomicLong(0); - consumer.streamStart(new BasicEntityDetails(-1, ContentType.APPLICATION_OCTET_STREAM), new FutureCallback() { - - @Override - public void completed(final byte[] result) { - count.incrementAndGet(); - } - - @Override - public void failed(final Exception ex) { - count.incrementAndGet(); - } - - @Override - public void cancelled() { - count.incrementAndGet(); - } - - }); + 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'})); @@ -109,7 +89,7 @@ public void cancelled() { consumer.streamEnd(null); Assertions.assertArrayEquals(new byte[] {'1', '2', '3', '4', '5'}, consumer.getContent()); - Assertions.assertEquals(1L, count.longValue()); + Assertions.assertEquals(1L, countingCallback.getCount()); } } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractBinAsyncEntityProducer.java b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractBinAsyncEntityProducer.java index 1dc5a48084..bb0c4d5fd2 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractBinAsyncEntityProducer.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractBinAsyncEntityProducer.java @@ -40,7 +40,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestAbstractBinAsyncEntityProducer { +class TestAbstractBinAsyncEntityProducer { static private class ChunkByteAsyncEntityProducer extends AbstractBinAsyncEntityProducer { @@ -83,7 +83,7 @@ public void failed(final Exception cause) { } @Test - public void testProduceDataNoBuffering() throws Exception { + void testProduceDataNoBuffering() throws Exception { final AsyncEntityProducer producer = new ChunkByteAsyncEntityProducer( 0, ContentType.TEXT_PLAIN, @@ -109,7 +109,7 @@ public void testProduceDataNoBuffering() throws Exception { } @Test - public void testProduceDataWithBuffering1() throws Exception { + void testProduceDataWithBuffering1() throws Exception { final AsyncEntityProducer producer = new ChunkByteAsyncEntityProducer( 5, ContentType.TEXT_PLAIN, @@ -139,7 +139,7 @@ public void testProduceDataWithBuffering1() throws Exception { } @Test - public void testProduceDataWithBuffering2() throws Exception { + void testProduceDataWithBuffering2() throws Exception { final AsyncEntityProducer producer = new ChunkByteAsyncEntityProducer( 5, ContentType.TEXT_PLAIN, diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractCharAsyncEntityConsumer.java b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractCharAsyncEntityConsumer.java index d5eb25e893..71f39b97b2 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractCharAsyncEntityConsumer.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractCharAsyncEntityConsumer.java @@ -31,9 +31,8 @@ import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.StandardCharsets; -import java.util.concurrent.atomic.AtomicLong; -import org.apache.hc.core5.concurrent.FutureCallback; +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; @@ -42,7 +41,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestAbstractCharAsyncEntityConsumer { +class TestAbstractCharAsyncEntityConsumer { static private class StringBuilderAsyncEntityConsumer extends AbstractCharAsyncEntityConsumer { @@ -80,29 +79,10 @@ public void releaseResources() { } @Test - public void testConsumeData() throws Exception { - + void testConsumeData() throws Exception { final AsyncEntityConsumer consumer = new StringBuilderAsyncEntityConsumer(); - - final AtomicLong count = new AtomicLong(0); - consumer.streamStart(new BasicEntityDetails(-1, ContentType.TEXT_PLAIN), new FutureCallback() { - - @Override - public void completed(final String result) { - count.incrementAndGet(); - } - - @Override - public void failed(final Exception ex) { - count.incrementAndGet(); - } - - @Override - public void cancelled() { - count.incrementAndGet(); - } - - }); + 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'})); @@ -112,33 +92,16 @@ public void cancelled() { consumer.streamEnd(null); Assertions.assertEquals("12345", consumer.getContent()); - Assertions.assertEquals(1L, count.longValue()); + Assertions.assertEquals(1L, countingCallback.getCount()); } @Test - public void testConsumeIncompleteData() throws Exception { + void testConsumeIncompleteData() throws Exception { final AsyncEntityConsumer consumer = new StringBuilderAsyncEntityConsumer(); - final AtomicLong count = new AtomicLong(0); - consumer.streamStart(new BasicEntityDetails(-1, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)), new FutureCallback() { - - @Override - public void completed(final String result) { - count.incrementAndGet(); - } - - @Override - public void failed(final Exception ex) { - count.incrementAndGet(); - } - - @Override - public void cancelled() { - count.incrementAndGet(); - } - - }); + 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); @@ -162,7 +125,7 @@ public void cancelled() { consumer.streamEnd(null); Assertions.assertEquals("stuff£stuff£stuff", consumer.getContent()); - Assertions.assertEquals(1L, count.longValue()); + Assertions.assertEquals(1L, countingCallback.getCount()); } } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractCharAsyncEntityProducer.java b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractCharAsyncEntityProducer.java index 743acbc516..f1497f2a14 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractCharAsyncEntityProducer.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAbstractCharAsyncEntityProducer.java @@ -40,7 +40,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestAbstractCharAsyncEntityProducer { +class TestAbstractCharAsyncEntityProducer { static private class ChunkCharAsyncEntityProducer extends AbstractCharAsyncEntityProducer { @@ -84,7 +84,7 @@ public void failed(final Exception cause) { } @Test - public void testProduceDataNoBuffering() throws Exception { + void testProduceDataNoBuffering() throws Exception { final AsyncEntityProducer producer = new ChunkCharAsyncEntityProducer( 256, 0, ContentType.TEXT_PLAIN, "this", "this and that"); @@ -108,7 +108,7 @@ public void testProduceDataNoBuffering() throws Exception { } @Test - public void testProduceDataWithBuffering() throws Exception { + void testProduceDataWithBuffering() throws Exception { final AsyncEntityProducer producer = new ChunkCharAsyncEntityProducer( 256, 5, ContentType.TEXT_PLAIN, "this", " and that", "all", " sorts of stuff"); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAsyncEntityProducers.java b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAsyncEntityProducers.java index c78859c873..237965305a 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAsyncEntityProducers.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestAsyncEntityProducers.java @@ -44,10 +44,10 @@ /** * Tests {@link AsyncEntityProducers}. */ -public class TestAsyncEntityProducers { +class TestAsyncEntityProducers { @Test - public void testPathEntityProducer() throws IOException { + 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); @@ -61,7 +61,7 @@ public void testPathEntityProducer() throws IOException { } @Test - public void testPathEntityProducerWithTrailers() throws IOException { + 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"); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestBasicAsyncEntityProducer.java b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestBasicAsyncEntityProducer.java index 072e13e427..0a66cf5965 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestBasicAsyncEntityProducer.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestBasicAsyncEntityProducer.java @@ -37,10 +37,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestBasicAsyncEntityProducer { +class TestBasicAsyncEntityProducer { @Test - public void testBinaryContent() throws Exception { + void testBinaryContent() throws Exception { final AsyncEntityProducer producer = new BasicAsyncEntityProducer( new byte[] { 'a', 'b', 'c' }, ContentType.DEFAULT_BINARY); @@ -59,7 +59,7 @@ public void testBinaryContent() throws Exception { } @Test - public void testTextContent() throws Exception { + void testTextContent() throws Exception { final AsyncEntityProducer producer = new BasicAsyncEntityProducer( "abc", ContentType.TEXT_PLAIN); @@ -78,7 +78,7 @@ public void testTextContent() throws Exception { } @Test - public void testTextContentRepeatable() throws Exception { + void testTextContentRepeatable() throws Exception { final AsyncEntityProducer producer = new BasicAsyncEntityProducer( "abc", ContentType.TEXT_PLAIN); @@ -100,7 +100,7 @@ public void testTextContentRepeatable() throws Exception { } @Test - public void testTextContentRepeatableUTF() throws Exception { + void testTextContentRepeatableUTF() throws Exception { final String content = ""; final AsyncEntityProducer producer = new BasicAsyncEntityProducer(content, ContentType.TEXT_XML); for (int i = 0; i < 3; i++) { diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestDigestingEntityConsumer.java b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestDigestingEntityConsumer.java index 55d7267007..2c61df6750 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestDigestingEntityConsumer.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestDigestingEntityConsumer.java @@ -33,10 +33,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestDigestingEntityConsumer { +class TestDigestingEntityConsumer { @Test - public void testConsumeData() throws Exception { + void testConsumeData() throws Exception { final DigestingEntityConsumer consumer = new DigestingEntityConsumer<>("MD5", new StringAsyncEntityConsumer()); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestDigestingEntityProducer.java b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestDigestingEntityProducer.java index b37f0d09d4..24b33931e4 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestDigestingEntityProducer.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestDigestingEntityProducer.java @@ -37,10 +37,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestDigestingEntityProducer { +class TestDigestingEntityProducer { @Test - public void testProduceData() throws Exception { + void testProduceData() throws Exception { final DigestingEntityProducer producer = new DigestingEntityProducer("MD5", new StringAsyncEntityProducer("12345", ContentType.TEXT_PLAIN)); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestFileAsyncEntityProducer.java b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestFileAsyncEntityProducer.java index f5a9992c6d..77ff3e94de 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestFileAsyncEntityProducer.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestFileAsyncEntityProducer.java @@ -43,12 +43,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class TestFileAsyncEntityProducer { +class TestFileAsyncEntityProducer { private File tempFile; @BeforeEach - public void setup() throws Exception { + void setup() throws Exception { tempFile = File.createTempFile("testing", ".txt"); try (final Writer writer = new OutputStreamWriter(new FileOutputStream(tempFile), StandardCharsets.US_ASCII)) { writer.append("abcdef"); @@ -57,14 +57,14 @@ public void setup() throws Exception { } @AfterEach - public void cleanup() { + void cleanup() { if (tempFile != null) { tempFile.delete(); tempFile = null; } } @Test - public void testTextContent() throws Exception { + void testTextContent() throws Exception { final AsyncEntityProducer producer = new FileEntityProducer(tempFile, ContentType.TEXT_PLAIN); @@ -83,7 +83,7 @@ public void testTextContent() throws Exception { } @Test - public void testTextContentRepeatable() throws Exception { + void testTextContentRepeatable() throws Exception { final AsyncEntityProducer producer = new FileEntityProducer(tempFile, ContentType.TEXT_PLAIN); Assertions.assertEquals(6, producer.getContentLength()); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestFileEntityProducer.java b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestFileEntityProducer.java index 4c2e579702..f01d6c7200 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestFileEntityProducer.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestFileEntityProducer.java @@ -38,10 +38,10 @@ /** * @since 5.0 */ -public class TestFileEntityProducer { +class TestFileEntityProducer { @Test - public void testFileLengthMaxIntPlusOne(@TempDir final Path tempFolder) throws IOException { + 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; diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestPathAsyncEntityProducer.java b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestPathAsyncEntityProducer.java index be62e435f3..211ddd7499 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestPathAsyncEntityProducer.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestPathAsyncEntityProducer.java @@ -45,12 +45,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class TestPathAsyncEntityProducer { +class TestPathAsyncEntityProducer { private File tempFile; @AfterEach - public void cleanup() { + void cleanup() { if (tempFile != null) { tempFile.delete(); tempFile = null; @@ -58,7 +58,7 @@ public void cleanup() { } @BeforeEach - public void setup() throws Exception { + void setup() throws Exception { tempFile = File.createTempFile("testing", ".txt"); try (final Writer writer = new OutputStreamWriter(new FileOutputStream(tempFile), StandardCharsets.US_ASCII)) { writer.append("abcdef"); @@ -67,7 +67,7 @@ public void setup() throws Exception { } @Test - public void testTextContent() throws Exception { + void testTextContent() throws Exception { final Path tempPath = tempFile.toPath(); final AsyncEntityProducer producer = new PathEntityProducer(tempPath, ContentType.TEXT_PLAIN, StandardOpenOption.READ); @@ -87,7 +87,7 @@ public void testTextContent() throws Exception { } @Test - public void testTextContentRepeatable() throws Exception { + void testTextContentRepeatable() throws Exception { final Path tempPath = tempFile.toPath(); final AsyncEntityProducer producer = new PathEntityProducer(tempPath, ContentType.TEXT_PLAIN, StandardOpenOption.READ); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestPathEntityProducer.java b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestPathEntityProducer.java index 61a1e13633..c1b02127c4 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestPathEntityProducer.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestPathEntityProducer.java @@ -39,10 +39,10 @@ /** * @since 5.0 */ -public class TestPathEntityProducer { +class TestPathEntityProducer { @Test - public void testFileLengthMaxIntPlusOne(@TempDir final Path tempFolder) throws IOException { + 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; diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestStringAsyncEntityProducer.java b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestStringAsyncEntityProducer.java index a89ea802cf..20df457384 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestStringAsyncEntityProducer.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/entity/TestStringAsyncEntityProducer.java @@ -37,10 +37,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestStringAsyncEntityProducer { +class TestStringAsyncEntityProducer { @Test - public void testTextContent() throws Exception { + void testTextContent() throws Exception { final AsyncEntityProducer producer = new StringAsyncEntityProducer( "abc", ContentType.TEXT_PLAIN); @@ -59,7 +59,7 @@ public void testTextContent() throws Exception { } @Test - public void testTextContentRepeatable() throws Exception { + void testTextContentRepeatable() throws Exception { final AsyncEntityProducer producer = new StringAsyncEntityProducer( "abc", ContentType.TEXT_PLAIN); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/ssl/BasicClientTlsStrategyTest.java b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/ssl/BasicClientTlsStrategyTest.java index 93aed83f49..4e3dd51c0f 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/ssl/BasicClientTlsStrategyTest.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/ssl/BasicClientTlsStrategyTest.java @@ -41,7 +41,7 @@ class BasicClientTlsStrategyTest { private SSLSessionVerifier sslSessionVerifier; @BeforeEach - public void prepareMocks() { + void prepareMocks() { MockitoAnnotations.openMocks(this); } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/ssl/BasicServerTlsStrategyTest.java b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/ssl/BasicServerTlsStrategyTest.java index eca95227e3..610d9ec409 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/ssl/BasicServerTlsStrategyTest.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/ssl/BasicServerTlsStrategyTest.java @@ -41,7 +41,7 @@ class BasicServerTlsStrategyTest { private SSLSessionVerifier sslSessionVerifier; @BeforeEach - public void prepareMocks() { + void prepareMocks() { MockitoAnnotations.openMocks(this); } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/support/classic/TestSharedInputBuffer.java b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/support/classic/TestSharedInputBuffer.java index 2bb8f81a1f..1335d9ca40 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/support/classic/TestSharedInputBuffer.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/support/classic/TestSharedInputBuffer.java @@ -43,12 +43,12 @@ import org.mockito.ArgumentMatchers; import org.mockito.Mockito; -public class TestSharedInputBuffer { +class TestSharedInputBuffer { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @Test - public void testBasis() throws Exception { + void testBasis() throws Exception { final Charset charset = StandardCharsets.US_ASCII; final SharedInputBuffer inputBuffer = new SharedInputBuffer(10); @@ -85,7 +85,7 @@ public void testBasis() throws Exception { } @Test - public void testMultithreadingRead() throws Exception { + void testMultithreadingRead() throws Exception { final SharedInputBuffer inputBuffer = new SharedInputBuffer(10); @@ -112,7 +112,7 @@ public void testMultithreadingRead() throws Exception { } @Test - public void testMultithreadingSingleRead() throws Exception { + void testMultithreadingSingleRead() throws Exception { final SharedInputBuffer inputBuffer = new SharedInputBuffer(10); @@ -136,7 +136,7 @@ public void testMultithreadingSingleRead() throws Exception { } @Test - public void testMultithreadingReadStream() throws Exception { + void testMultithreadingReadStream() throws Exception { final SharedInputBuffer inputBuffer = new SharedInputBuffer(10); @@ -175,7 +175,7 @@ public void testMultithreadingReadStream() throws Exception { } @Test - public void testMultithreadingReadStreamAbort() throws Exception { + void testMultithreadingReadStreamAbort() throws Exception { final SharedInputBuffer inputBuffer = new SharedInputBuffer(10); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/support/classic/TestSharedOutputBuffer.java b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/support/classic/TestSharedOutputBuffer.java index 35e8d55dba..dd33259aeb 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/nio/support/classic/TestSharedOutputBuffer.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/nio/support/classic/TestSharedOutputBuffer.java @@ -37,16 +37,18 @@ 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; -public class TestSharedOutputBuffer { +class TestSharedOutputBuffer { private static final Timeout TIMEOUT = Timeout.ofMinutes(1); @@ -54,24 +56,35 @@ 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 synchronized int write(final ByteBuffer src) throws IOException { - return channel.write(src); + public int write(final ByteBuffer src) throws IOException { + lock.lock(); + try { + return channel.write(src); + } finally { + lock.unlock(); + } } @Override - public synchronized void requestOutput() { - notifyAll(); + public void requestOutput() { } @Override - public synchronized void endStream(final List trailers) throws IOException { - channel.close(); - notifyAll(); + public void endStream(final List trailers) throws IOException { + lock.lock(); + try { + channel.close(); + } finally { + lock.unlock(); + } } @Override @@ -79,14 +92,10 @@ public void endStream() throws IOException { endStream(null); } - public synchronized void awaitOutputRequest() throws InterruptedException { - wait(); - } - } @Test - public void testBasis() throws Exception { + void testBasis() throws Exception { final Charset charset = StandardCharsets.US_ASCII; final SharedOutputBuffer outputBuffer = new SharedOutputBuffer(30); @@ -113,7 +122,7 @@ public void testBasis() throws Exception { } @Test - public void testFlush() throws Exception { + void testFlush() throws Exception { final Charset charset = StandardCharsets.US_ASCII; final SharedOutputBuffer outputBuffer = new SharedOutputBuffer(30); @@ -137,8 +146,8 @@ public void testFlush() throws Exception { Assertions.assertEquals(30, outputBuffer.capacity()); } - @Test - public void testMultithreadingWriteStream() throws Exception { + @RepeatedTest(20) + void testMultithreadingWriteStream() throws Exception { final Charset charset = StandardCharsets.US_ASCII; final SharedOutputBuffer outputBuffer = new SharedOutputBuffer(20); @@ -147,40 +156,41 @@ public void testMultithreadingWriteStream() throws Exception { final DataStreamChannelMock dataStreamChannel = new DataStreamChannelMock(channel); final ExecutorService executorService = Executors.newFixedThreadPool(2); - 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; - } - if (!outputBuffer.hasData()) { - dataStreamChannel.awaitOutputRequest(); + 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; - }); + 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(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)); + Assertions.assertEquals("1234567890123456789012123456789012345678901234567890", new String(channel.toByteArray(), charset)); + } finally { + executorService.shutdownNow(); + } } @Test - public void testMultithreadingWriteStreamAbort() throws Exception { + void testMultithreadingWriteStreamAbort() throws Exception { final Charset charset = StandardCharsets.US_ASCII; final SharedOutputBuffer outputBuffer = new SharedOutputBuffer(20); @@ -209,7 +219,7 @@ public void testMultithreadingWriteStreamAbort() throws Exception { } @Test - public void testEndStreamOnlyCalledOnce() throws IOException { + void testEndStreamOnlyCalledOnce() throws IOException { final DataStreamChannel channel = Mockito.mock(DataStreamChannel.class); final SharedOutputBuffer outputBuffer = new SharedOutputBuffer(20); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestChainBuilder.java b/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestChainBuilder.java index 5955033e0a..c53cbf92cd 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestChainBuilder.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestChainBuilder.java @@ -34,10 +34,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestChainBuilder { +class TestChainBuilder { @Test - public void testBuildChain() throws Exception { + void testBuildChain() { final ChainBuilder cb = new ChainBuilder<>(); final HttpRequestInterceptor i1 = RequestContent.INSTANCE; final HttpRequestInterceptor i2 = RequestTargetHost.INSTANCE; diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestForwardedRequest.java b/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestForwardedRequest.java new file mode 100644 index 0000000000..c9e51d5903 --- /dev/null +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestForwardedRequest.java @@ -0,0 +1,196 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * 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)); + } +} diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestHttpExecutionContext.java b/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestHttpExecutionContext.java index 3a6f0df31f..2023f4adc7 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestHttpExecutionContext.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestHttpExecutionContext.java @@ -32,12 +32,12 @@ // the name of this test is historic, the implementation classes of HttpContext // have been renamed to BasicHttpContext and SyncBasicHttpContext -public class TestHttpExecutionContext { +class TestHttpExecutionContext { @Test - public void testContextOperations() { - final HttpContext parentContext = new BasicHttpContext(null); - final HttpContext currentContext = new BasicHttpContext(parentContext); + void testContextOperations() { + final HttpCoreContext parentContext = new HttpCoreContext(); + final HttpCoreContext currentContext = new HttpCoreContext(parentContext); parentContext.setAttribute("param1", "1"); parentContext.setAttribute("param2", "2"); @@ -65,19 +65,11 @@ public void testContextOperations() { } @Test - public void testEmptyContextOperations() { - final HttpContext currentContext = new BasicHttpContext(null); + void testEmptyContextOperations() { + final HttpCoreContext currentContext = new HttpCoreContext(); Assertions.assertNull(currentContext.getAttribute("param1")); currentContext.removeAttribute("param1"); Assertions.assertNull(currentContext.getAttribute("param1")); } - @Test - public void testContextInvalidInput() throws Exception { - final HttpContext currentContext = new BasicHttpContext(null); - Assertions.assertThrows(NullPointerException.class, () -> currentContext.setAttribute(null, null)); - Assertions.assertThrows(NullPointerException.class, () -> currentContext.getAttribute(null)); - Assertions.assertThrows(NullPointerException.class, () -> currentContext.removeAttribute(null)); - } - } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestRequestHandlerRegistry.java b/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestRequestHandlerRegistry.java deleted file mode 100644 index 0bc62df516..0000000000 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestRequestHandlerRegistry.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package org.apache.hc.core5.http.protocol; - -import org.apache.hc.core5.http.HttpHost; -import org.apache.hc.core5.http.Method; -import org.apache.hc.core5.http.MisdirectedRequestException; -import org.apache.hc.core5.http.message.BasicHttpRequest; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class TestRequestHandlerRegistry { - - private RequestHandlerRegistry handlerRegistry; - private HttpContext context; - - @BeforeEach - public void setUp() { - handlerRegistry = new RequestHandlerRegistry<>("myhost", UriPatternMatcher::new); - context = new BasicHttpContext(); - } - - @Test - public void testResolveByRequestUri() throws Exception { - handlerRegistry.register(null, "/test*", "stuff"); - Assertions.assertNotEquals("stuff", handlerRegistry.resolve(new BasicHttpRequest(Method.GET, "/"), context)); - Assertions.assertNotEquals("stuff", handlerRegistry.resolve(new BasicHttpRequest(Method.GET, "/abc"), context)); - Assertions.assertEquals("stuff", handlerRegistry.resolve(new BasicHttpRequest(Method.GET, "/test"), context)); - Assertions.assertEquals("stuff", handlerRegistry.resolve(new BasicHttpRequest(Method.GET, "/testabc"), context)); - } - - @Test - public void testByRequestUriWithQuery() throws Exception { - handlerRegistry.register(null, "/test*", "stuff"); - Assertions.assertEquals("stuff", handlerRegistry.resolve(new BasicHttpRequest(Method.GET, "/test?test=a"), context)); - Assertions.assertNotEquals("stuff", handlerRegistry.resolve(new BasicHttpRequest(Method.GET, "/tes?test=a"), context)); - } - - @Test - public void testResolveByHostnameAndRequestUri() throws Exception { - handlerRegistry.register("myhost", "/test*", "stuff"); - Assertions.assertEquals("stuff", handlerRegistry.resolve(new BasicHttpRequest(Method.GET, new HttpHost("myhost"), "/test"), context)); - Assertions.assertEquals("stuff", handlerRegistry.resolve(new BasicHttpRequest(Method.GET, new HttpHost("MyHost"), "/testabc"), context)); - } - - @Test - public void testResolveByLocalhostAndRequestUri() throws Exception { - handlerRegistry.register("myhost", "/test*", "stuff"); - Assertions.assertEquals("stuff", handlerRegistry.resolve(new BasicHttpRequest(Method.GET, new HttpHost("localhost"), "/test"), context)); - Assertions.assertEquals("stuff", handlerRegistry.resolve(new BasicHttpRequest(Method.GET, new HttpHost("LocalHost"), "/testabc"), context)); - Assertions.assertEquals("stuff", handlerRegistry.resolve(new BasicHttpRequest(Method.GET, new HttpHost("127.0.0.1"), "/testabc"), context)); - } - - @Test - public void testResolveByUnknownHostname() throws Exception { - handlerRegistry.register("myhost", "/test*", "stuff"); - Assertions.assertThrows(MisdirectedRequestException.class, () -> - handlerRegistry.resolve(new BasicHttpRequest(Method.GET, new HttpHost("otherhost"), "/test"), context)); - } - -} diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestStandardInterceptors.java b/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestStandardInterceptors.java index a28328780f..bdf336a0df 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestStandardInterceptors.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestStandardInterceptors.java @@ -29,6 +29,8 @@ 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; @@ -38,6 +40,7 @@ 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; @@ -49,12 +52,13 @@ import org.apache.hc.core5.net.URIAuthority; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; -public class TestStandardInterceptors { +class TestStandardInterceptors { @Test - public void testRequestConnControlGenerated() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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); @@ -64,8 +68,8 @@ public void testRequestConnControlGenerated() throws Exception { } @Test - public void testRequestConnControlConnectMethod() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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); @@ -74,8 +78,8 @@ public void testRequestConnControlConnectMethod() throws Exception { } @Test - public void testRequestConnControlCustom() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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); @@ -88,8 +92,8 @@ public void testRequestConnControlCustom() throws Exception { } @Test - public void testRequestConnControlUpgrade() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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; @@ -100,14 +104,14 @@ public void testRequestConnControlUpgrade() throws Exception { } @Test - public void testRequestConnControlInvalidInput() throws Exception { + void testRequestConnControlInvalidInput() { final HttpRequestInterceptor interceptor = RequestConnControl.INSTANCE; Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); } @Test - public void testRequestContentProtocolException() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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, "/"); @@ -121,20 +125,32 @@ public void testRequestContentProtocolException() throws Exception { } @Test - public void testRequestContentNullEntity() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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.assertNull(header); + Assertions.assertNotNull(header); + Assertions.assertEquals(0, Integer.parseInt(header.getValue())); Assertions.assertNull(request.getFirstHeader(HttpHeaders.TRANSFER_ENCODING)); } @Test - public void testRequestContentEntityContentLengthDelimitedHTTP11() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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)); @@ -147,8 +163,8 @@ public void testRequestContentEntityContentLengthDelimitedHTTP11() throws Except } @Test - public void testRequestContentEntityChunkedHTTP11() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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)); @@ -161,8 +177,8 @@ public void testRequestContentEntityChunkedHTTP11() throws Exception { } @Test - public void testRequestContentEntityUnknownLengthHTTP11() throws Exception { - final HttpContext context = new BasicHttpContext(null); + void testRequestContentEntityUnknownLengthHTTP11() throws Exception { + final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.setEntity(new BasicHttpEntity(EmptyInputStream.INSTANCE, -1, null)); @@ -175,8 +191,8 @@ public void testRequestContentEntityUnknownLengthHTTP11() throws Exception { } @Test - public void testRequestContentEntityChunkedHTTP10() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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)); @@ -187,8 +203,8 @@ public void testRequestContentEntityChunkedHTTP10() throws Exception { } @Test - public void testRequestContentTypeAndEncoding() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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")); @@ -204,8 +220,8 @@ public void testRequestContentTypeAndEncoding() throws Exception { } @Test - public void testRequestContentNullTypeAndEncoding() throws Exception { - final HttpContext context = new BasicHttpContext(null); + void testRequestContentNullTypeAndEncoding() throws Exception { + final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.setEntity(new BasicHttpEntity(EmptyInputStream.INSTANCE, null, null)); @@ -216,8 +232,8 @@ public void testRequestContentNullTypeAndEncoding() throws Exception { } @Test - public void testRequestContentEntityUnknownLengthHTTP10() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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)); @@ -228,25 +244,25 @@ public void testRequestContentEntityUnknownLengthHTTP10() throws Exception { } @Test - public void testRequestContentInvalidInput() throws Exception { + void testRequestContentInvalidInput() { final HttpRequestInterceptor interceptor = RequestContent.INSTANCE; Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); } @Test - public void testRequestContentIgnoreNonenclosingRequests() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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(0, request.getHeaders().length); + Assertions.assertEquals(1, request.getHeaders().length); } @Test - public void testRequestContentOverwriteHeaders() throws Exception { + void testRequestContentOverwriteHeaders() throws Exception { final RequestContent interceptor = new RequestContent(true); - final HttpContext context = new BasicHttpContext(null); + 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")); @@ -258,9 +274,9 @@ public void testRequestContentOverwriteHeaders() throws Exception { } @Test - public void testRequestContentAddHeaders() throws Exception { + void testRequestContentAddHeaders() throws Exception { final RequestContent interceptor = new RequestContent(true); - final HttpContext context = new BasicHttpContext(null); + final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.setEntity(new StringEntity("")); interceptor.process(request, request.getEntity(), context); @@ -272,8 +288,8 @@ public void testRequestContentAddHeaders() throws Exception { } @Test - public void testRequestContentEntityWithTrailers() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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"))); @@ -288,8 +304,8 @@ public void testRequestContentEntityWithTrailers() throws Exception { } @Test - public void testRequestContentTraceWithEntity() throws Exception { - final HttpContext context = new BasicHttpContext(null); + void testRequestContentTraceWithEntity() { + final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.TRACE, "/"); request.setEntity(new StringEntity("stuff")); final HttpRequestInterceptor interceptor = RequestContent.INSTANCE; @@ -298,7 +314,7 @@ public void testRequestContentTraceWithEntity() throws Exception { } @Test - public void testRequestExpectContinueGenerated() throws Exception { + void testRequestExpectContinueGenerated() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.setEntity(new StringEntity("whatever", StandardCharsets.US_ASCII)); @@ -310,7 +326,7 @@ public void testRequestExpectContinueGenerated() throws Exception { } @Test - public void testRequestExpectContinueHTTP10() throws Exception { + void testRequestExpectContinueHTTP10() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); context.setProtocolVersion(HttpVersion.HTTP_1_0); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); @@ -322,7 +338,7 @@ public void testRequestExpectContinueHTTP10() throws Exception { } @Test - public void testRequestExpectContinueZeroContent() throws Exception { + void testRequestExpectContinueZeroContent() throws Exception { final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.setEntity(new StringEntity("", StandardCharsets.US_ASCII)); @@ -333,15 +349,15 @@ public void testRequestExpectContinueZeroContent() throws Exception { } @Test - public void testRequestExpectContinueInvalidInput() throws Exception { + void testRequestExpectContinueInvalidInput() { final RequestExpectContinue interceptor = RequestExpectContinue.INSTANCE; Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); } @Test - public void testRequestExpectContinueIgnoreNonenclosingRequests() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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); @@ -349,8 +365,8 @@ public void testRequestExpectContinueIgnoreNonenclosingRequests() throws Excepti } @Test - public void testRequestTargetHostGenerated() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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; @@ -361,8 +377,8 @@ public void testRequestTargetHostGenerated() throws Exception { } @Test - public void testRequestTargetHostNotGenerated() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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")); @@ -374,8 +390,8 @@ public void testRequestTargetHostNotGenerated() throws Exception { } @Test - public void testRequestTargetHostMissingHostHTTP10() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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; @@ -385,8 +401,8 @@ public void testRequestTargetHostMissingHostHTTP10() throws Exception { } @Test - public void testRequestTargetHostMissingHostHTTP11() throws Exception { - final HttpContext context = new BasicHttpContext(null); + void testRequestTargetHostMissingHostHTTP11() { + final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); final HttpRequestInterceptor interceptor = RequestTargetHost.INSTANCE; Assertions.assertThrows(ProtocolException.class, () -> @@ -394,7 +410,7 @@ public void testRequestTargetHostMissingHostHTTP11() throws Exception { } @Test - public void testRequestTargetHostInvalidInput() throws Exception { + void testRequestTargetHostInvalidInput() { final HttpRequestInterceptor interceptor = RequestTargetHost.INSTANCE; Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); @@ -403,8 +419,8 @@ public void testRequestTargetHostInvalidInput() throws Exception { } @Test - public void testRequestTargetHostConnectHttp11() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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; @@ -415,8 +431,8 @@ public void testRequestTargetHostConnectHttp11() throws Exception { } @Test - public void testRequestTargetHostConnectHttp10() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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)); @@ -427,8 +443,8 @@ public void testRequestTargetHostConnectHttp10() throws Exception { } @Test - public void testRequestUserAgentGenerated() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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); @@ -438,8 +454,8 @@ public void testRequestUserAgentGenerated() throws Exception { } @Test - public void testRequestUserAgentNotGenerated() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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"); @@ -450,8 +466,8 @@ public void testRequestUserAgentNotGenerated() throws Exception { } @Test - public void testRequestUserAgentMissingUserAgent() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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); @@ -460,14 +476,14 @@ public void testRequestUserAgentMissingUserAgent() throws Exception { } @Test - public void testRequestUserAgentInvalidInput() throws Exception { + void testRequestUserAgentInvalidInput() { final HttpRequestInterceptor interceptor = RequestUserAgent.INSTANCE; Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); } @Test - public void testResponseConnControlNoEntity() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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); @@ -476,8 +492,8 @@ public void testResponseConnControlNoEntity() throws Exception { } @Test - public void testResponseConnControlEntityContentLength() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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(); @@ -487,11 +503,11 @@ public void testResponseConnControlEntityContentLength() throws Exception { } @Test - public void testResponseConnControlEntityUnknownContentLengthExplicitKeepAlive() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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.setAttribute(HttpCoreContext.HTTP_REQUEST, request); + context.setRequest(request); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setEntity(new BasicHttpEntity(EmptyInputStream.INSTANCE, null)); final ResponseConnControl interceptor = new ResponseConnControl(); @@ -502,8 +518,8 @@ public void testResponseConnControlEntityUnknownContentLengthExplicitKeepAlive() } @Test - public void testResponseConnControlEntityChunked() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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(); @@ -513,12 +529,12 @@ public void testResponseConnControlEntityChunked() throws Exception { } @Test - public void testResponseConnControlEntityUnknownContentLengthHTTP10() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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.setAttribute(HttpCoreContext.HTTP_REQUEST, request); + context.setRequest(request); final BasicClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setEntity(new BasicHttpEntity(EmptyInputStream.INSTANCE, null)); @@ -530,11 +546,11 @@ public void testResponseConnControlEntityUnknownContentLengthHTTP10() throws Exc } @Test - public void testResponseConnControlClientRequest() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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.setAttribute(HttpCoreContext.HTTP_REQUEST, request); + context.setRequest(request); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setEntity(new StringEntity("whatever")); @@ -546,10 +562,10 @@ public void testResponseConnControlClientRequest() throws Exception { } @Test - public void testResponseConnControlClientRequest2() throws Exception { - final HttpContext context = new BasicHttpContext(null); + void testResponseConnControlClientRequest2() throws Exception { + final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); - context.setAttribute(HttpCoreContext.HTTP_REQUEST, request); + context.setRequest(request); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setEntity(new StringEntity("whatever")); @@ -560,11 +576,11 @@ public void testResponseConnControlClientRequest2() throws Exception { } @Test - public void testResponseConnControl10Client11Response() throws Exception { - final HttpContext context = new BasicHttpContext(null); + void testResponseConnControl10Client11Response() throws Exception { + final HttpCoreContext context = HttpCoreContext.create(); context.setProtocolVersion(HttpVersion.HTTP_1_0); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); - context.setAttribute(HttpCoreContext.HTTP_REQUEST, request); + context.setRequest(request); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setEntity(new StringEntity("whatever")); @@ -576,11 +592,11 @@ public void testResponseConnControl10Client11Response() throws Exception { } @Test - public void testResponseConnControlStatusCode() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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.setAttribute(HttpCoreContext.HTTP_REQUEST, request); + context.setRequest(request); final ResponseConnControl interceptor = new ResponseConnControl(); @@ -604,11 +620,11 @@ public void testResponseConnControlStatusCode() throws Exception { } @Test - public void testResponseConnControlExplicitClose() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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.setAttribute(HttpCoreContext.HTTP_REQUEST, request); + context.setRequest(request); final ResponseConnControl interceptor = new ResponseConnControl(); @@ -621,11 +637,11 @@ public void testResponseConnControlExplicitClose() throws Exception { } @Test - public void testResponseConnControlClientRequestMixUp() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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.setAttribute(HttpCoreContext.HTTP_REQUEST, request); + context.setRequest(request); final ResponseConnControl interceptor = new ResponseConnControl(); @@ -637,8 +653,8 @@ public void testResponseConnControlClientRequestMixUp() throws Exception { } @Test - public void testResponseConnControlUpgrade() throws Exception { - final HttpContext context = new BasicHttpContext(null); + void testResponseConnControlUpgrade() throws Exception { + final HttpCoreContext context = HttpCoreContext.create(); final ResponseConnControl interceptor = new ResponseConnControl(); @@ -651,7 +667,7 @@ public void testResponseConnControlUpgrade() throws Exception { } @Test - public void testResponseConnControlHostInvalidInput() throws Exception { + void testResponseConnControlHostInvalidInput() { final ResponseConnControl interceptor = new ResponseConnControl(); Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); @@ -661,8 +677,8 @@ public void testResponseConnControlHostInvalidInput() throws Exception { } @Test - public void testResponseContentNoEntity() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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); @@ -672,8 +688,8 @@ public void testResponseContentNoEntity() throws Exception { } @Test - public void testResponseContentStatusNoContent() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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(); @@ -683,8 +699,8 @@ public void testResponseContentStatusNoContent() throws Exception { } @Test - public void testResponseContentStatusNotModified() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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(); @@ -694,8 +710,8 @@ public void testResponseContentStatusNotModified() throws Exception { } @Test - public void testResponseContentEntityChunked() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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(); @@ -708,8 +724,8 @@ public void testResponseContentEntityChunked() throws Exception { } @Test - public void testResponseContentEntityContentLenghtDelimited() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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(); @@ -722,8 +738,8 @@ public void testResponseContentEntityContentLenghtDelimited() throws Exception { } @Test - public void testResponseContentEntityUnknownContentLength() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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(); @@ -736,8 +752,8 @@ public void testResponseContentEntityUnknownContentLength() throws Exception { } @Test - public void testResponseContentEntityChunkedHTTP10() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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)); @@ -750,8 +766,8 @@ public void testResponseContentEntityChunkedHTTP10() throws Exception { } @Test - public void testResponseContentEntityNoContentTypeAndEncoding() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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(); @@ -763,8 +779,8 @@ public void testResponseContentEntityNoContentTypeAndEncoding() throws Exception } @Test - public void testResponseContentEntityContentTypeAndEncoding() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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")); @@ -779,15 +795,15 @@ public void testResponseContentEntityContentTypeAndEncoding() throws Exception { } @Test - public void testResponseContentInvalidInput() throws Exception { + void testResponseContentInvalidInput() { final ResponseContent interceptor = new ResponseContent(); Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); } @Test - public void testResponseContentInvalidResponseState() throws Exception { + void testResponseContentInvalidResponseState() { final ResponseContent interceptor = new ResponseContent(); - final HttpContext context = new BasicHttpContext(null); + 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, () -> @@ -799,9 +815,9 @@ public void testResponseContentInvalidResponseState() throws Exception { } @Test - public void testResponseContentOverwriteHeaders() throws Exception { + void testResponseContentOverwriteHeaders() throws Exception { final ResponseContent interceptor = new ResponseContent(true); - final HttpContext context = new BasicHttpContext(null); + 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")); @@ -810,9 +826,9 @@ public void testResponseContentOverwriteHeaders() throws Exception { } @Test - public void testResponseContentAddHeaders() throws Exception { + void testResponseContentAddHeaders() throws Exception { final ResponseContent interceptor = new ResponseContent(true); - final HttpContext context = new BasicHttpContext(null); + 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()); @@ -820,8 +836,8 @@ public void testResponseContentAddHeaders() throws Exception { } @Test - public void testResponseContentEntityWithTrailers() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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"))); @@ -836,8 +852,8 @@ public void testResponseContentEntityWithTrailers() throws Exception { } @Test - public void testResponseDateGenerated() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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); @@ -849,8 +865,8 @@ public void testResponseDateGenerated() throws Exception { } @Test - public void testResponseDateNotGenerated() throws Exception { - final HttpContext context = new BasicHttpContext(null); + void testResponseDateNotGenerated() throws Exception { + final HttpCoreContext context = HttpCoreContext.create(); final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); response.setCode(199); final ResponseDate interceptor = new ResponseDate(); @@ -860,15 +876,15 @@ public void testResponseDateNotGenerated() throws Exception { } @Test - public void testResponseDateInvalidInput() throws Exception { + void testResponseDateInvalidInput() { final ResponseDate interceptor = new ResponseDate(); Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); } @Test - public void testRequestDateGenerated() throws Exception { - final HttpContext context = new BasicHttpContext(null); + void testRequestDateGenerated() throws Exception { + final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/"); request.setEntity(new StringEntity("stuff")); @@ -882,8 +898,8 @@ public void testRequestDateGenerated() throws Exception { } @Test - public void testRequestDateNotGenerated() throws Exception { - final HttpContext context = new BasicHttpContext(null); + void testRequestDateNotGenerated() throws Exception { + final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); final HttpRequestInterceptor interceptor = RequestDate.INSTANCE; @@ -893,15 +909,15 @@ public void testRequestDateNotGenerated() throws Exception { } @Test - public void testRequestDateInvalidInput() throws Exception { + void testRequestDateInvalidInput() { final HttpRequestInterceptor interceptor = RequestDate.INSTANCE; Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); } @Test - public void testResponseServerGenerated() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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); @@ -911,8 +927,8 @@ public void testResponseServerGenerated() throws Exception { } @Test - public void testResponseServerNotGenerated() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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"); @@ -923,8 +939,8 @@ public void testResponseServerNotGenerated() throws Exception { } @Test - public void testResponseServerMissing() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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); @@ -933,23 +949,23 @@ public void testResponseServerMissing() throws Exception { } @Test - public void testResponseServerInvalidInput() throws Exception { + void testResponseServerInvalidInput() { final ResponseServer interceptor = new ResponseServer(); Assertions.assertThrows(NullPointerException.class, () -> interceptor.process(null, null, null)); } @Test - public void testRequestHttp10HostHeaderAbsent() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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(); - interceptor.process(request, request.getEntity(), context); + Assertions.assertDoesNotThrow(() -> interceptor.process(request, request.getEntity(), context)); } @Test - public void testRequestHttpHostHeader() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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"); @@ -959,8 +975,8 @@ public void testRequestHttpHostHeader() throws Exception { } @Test - public void testRequestHttpHostHeaderNoPort() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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"); @@ -970,8 +986,8 @@ public void testRequestHttpHostHeaderNoPort() throws Exception { } @Test - public void testRequestHttp11HostHeaderPresent() throws Exception { - final HttpContext context = new BasicHttpContext(null); + 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(); @@ -979,8 +995,8 @@ public void testRequestHttp11HostHeaderPresent() throws Exception { } @Test - public void testRequestHttp11HostHeaderAbsent() throws Exception { - final HttpContext context = new BasicHttpContext(null); + void testRequestHttp11HostHeaderAbsent() { + final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); final RequestValidateHost interceptor = new RequestValidateHost(); Assertions.assertThrows(ProtocolException.class, () -> @@ -988,8 +1004,8 @@ public void testRequestHttp11HostHeaderAbsent() throws Exception { } @Test - public void testRequestHttp11MultipleHostHeaders() throws Exception { - final HttpContext context = new BasicHttpContext(null); + void testRequestHttp11MultipleHostHeaders() { + final HttpCoreContext context = HttpCoreContext.create(); final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/"); request.addHeader(HttpHeaders.HOST, "blah"); request.addHeader(HttpHeaders.HOST, "blah"); @@ -998,4 +1014,121 @@ public void testRequestHttp11MultipleHostHeaders() throws Exception { 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)); + } + } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestUriPatternMatcher.java b/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestUriPatternMatcher.java deleted file mode 100644 index 395a240935..0000000000 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestUriPatternMatcher.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package org.apache.hc.core5.http.protocol; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class TestUriPatternMatcher { - - @Test - public void testEntrySet() throws Exception { - final Object h1 = new Object(); - final Object h2 = new Object(); - final Object h3 = new Object(); - - final UriPatternMatcher matcher = new UriPatternMatcher<>(); - Assertions.assertEquals(0, matcher.entrySet().size()); - matcher.register("/h1", h1); - Assertions.assertEquals(1, matcher.entrySet().size()); - matcher.register("/h2", h2); - Assertions.assertEquals(2, matcher.entrySet().size()); - matcher.register("/h3", h3); - Assertions.assertEquals(3, matcher.entrySet().size()); - } - - @Test - public void testRegisterUnregister() throws Exception { - final Object h1 = new Object(); - final Object h2 = new Object(); - final Object h3 = new Object(); - - final LookupRegistry matcher = new UriPatternMatcher<>(); - matcher.register("/h1", h1); - matcher.register("/h2", h2); - matcher.register("/h3", h3); - - Object h; - - h = matcher.lookup("/h1"); - Assertions.assertNotNull(h); - Assertions.assertSame(h1, h); - h = matcher.lookup("/h2"); - Assertions.assertNotNull(h); - Assertions.assertSame(h2, h); - h = matcher.lookup("/h3"); - Assertions.assertNotNull(h); - Assertions.assertSame(h3, h); - - matcher.unregister("/h1"); - h = matcher.lookup("/h1"); - Assertions.assertNull(h); - } - - @Test - public void testRegisterNull() throws Exception { - final LookupRegistry matcher = new UriPatternMatcher<>(); - Assertions.assertThrows(NullPointerException.class, () -> - matcher.register(null, null)); - } - - @Test - public void testWildCardMatching1() throws Exception { - final Object h1 = new Object(); - final Object h2 = new Object(); - final Object h3 = new Object(); - final Object def = new Object(); - - final LookupRegistry matcher = new UriPatternMatcher<>(); - matcher.register("*", def); - matcher.register("/one/*", h1); - matcher.register("/one/two/*", h2); - matcher.register("/one/two/three/*", h3); - - Object h; - - h = matcher.lookup("/one/request"); - Assertions.assertNotNull(h); - Assertions.assertSame(h1, h); - - h = matcher.lookup("/one/two/request"); - Assertions.assertNotNull(h); - Assertions.assertSame(h2, h); - - h = matcher.lookup("/one/two/three/request"); - Assertions.assertNotNull(h); - Assertions.assertSame(h3, h); - - h = matcher.lookup("default/request"); - Assertions.assertNotNull(h); - Assertions.assertSame(def, h); - } - - @Test - public void testWildCardMatching2() throws Exception { - final Object h1 = new Object(); - final Object h2 = new Object(); - final Object def = new Object(); - - final LookupRegistry matcher = new UriPatternMatcher<>(); - matcher.register("*", def); - matcher.register("*.view", h1); - matcher.register("*.form", h2); - - Object h; - - h = matcher.lookup("/that.view"); - Assertions.assertNotNull(h); - Assertions.assertSame(h1, h); - - h = matcher.lookup("/that.form"); - Assertions.assertNotNull(h); - Assertions.assertSame(h2, h); - - h = matcher.lookup("/whatever"); - Assertions.assertNotNull(h); - Assertions.assertSame(def, h); - } - - @Test - public void testSuffixPatternOverPrefixPatternMatch() throws Exception { - final Object h1 = new Object(); - final Object h2 = new Object(); - - final LookupRegistry matcher = new UriPatternMatcher<>(); - matcher.register("/ma*", h1); - matcher.register("*tch", h2); - - final Object h = matcher.lookup("/match"); - Assertions.assertNotNull(h); - Assertions.assertSame(h1, h); - } - - @Test - public void testRegisterInvalidInput() throws Exception { - final LookupRegistry matcher = new UriPatternMatcher<>(); - Assertions.assertThrows(NullPointerException.class, () -> - matcher.register(null, null)); - } - - @Test - public void testLookupInvalidInput() throws Exception { - final LookupRegistry matcher = new UriPatternMatcher<>(); - Assertions.assertThrows(NullPointerException.class, () -> - matcher.lookup(null)); - } - - @Test - public void testMatchExact() { - final Object h1 = new Object(); - final Object h2 = new Object(); - - final LookupRegistry matcher = new UriPatternMatcher<>(); - matcher.register("exact", h1); - matcher.register("*", h2); - - final Object h = matcher.lookup("exact"); - Assertions.assertNotNull(h); - Assertions.assertSame(h1, h); - } - -} diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestUriPatternOrderedMatcher.java b/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestUriPatternOrderedMatcher.java deleted file mode 100644 index 604ad659cf..0000000000 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestUriPatternOrderedMatcher.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package org.apache.hc.core5.http.protocol; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class TestUriPatternOrderedMatcher { - - @Test - public void testEntrySet() throws Exception { - final Object h1 = new Object(); - final Object h2 = new Object(); - final Object h3 = new Object(); - - final UriPatternOrderedMatcher matcher = new UriPatternOrderedMatcher<>(); - Assertions.assertEquals(0, matcher.entrySet().size()); - matcher.register("/h1", h1); - Assertions.assertEquals(1, matcher.entrySet().size()); - matcher.register("/h2", h2); - Assertions.assertEquals(2, matcher.entrySet().size()); - matcher.register("/h3", h3); - Assertions.assertEquals(3, matcher.entrySet().size()); - } - - @Test - public void testRegisterUnregister() throws Exception { - final Object h1 = new Object(); - final Object h2 = new Object(); - final Object h3 = new Object(); - - final LookupRegistry matcher = new UriPatternOrderedMatcher<>(); - matcher.register("/h1", h1); - matcher.register("/h2", h2); - matcher.register("/h3", h3); - - Object h; - - h = matcher.lookup("/h1"); - Assertions.assertNotNull(h); - Assertions.assertSame(h1, h); - h = matcher.lookup("/h2"); - Assertions.assertNotNull(h); - Assertions.assertSame(h2, h); - h = matcher.lookup("/h3"); - Assertions.assertNotNull(h); - Assertions.assertSame(h3, h); - - matcher.unregister("/h1"); - h = matcher.lookup("/h1"); - Assertions.assertNull(h); - } - - @Test - public void testRegisterNull() throws Exception { - final LookupRegistry matcher = new UriPatternOrderedMatcher<>(); - Assertions.assertThrows(NullPointerException.class, () -> - matcher.register(null, null)); - } - - @Test - public void testWildCardMatching1() throws Exception { - final Object h1 = new Object(); - final Object h2 = new Object(); - final Object h3 = new Object(); - final Object def = new Object(); - - final LookupRegistry matcher = new UriPatternOrderedMatcher<>(); - matcher.register("*", def); - matcher.register("/one/*", h1); - matcher.register("/one/two/*", h2); - matcher.register("/one/two/three/*", h3); - - Object h; - - h = matcher.lookup("/one/request"); - Assertions.assertNotNull(h); - Assertions.assertSame(def, h); - - h = matcher.lookup("/one/two/request"); - Assertions.assertNotNull(h); - Assertions.assertSame(def, h); - - h = matcher.lookup("/one/two/three/request"); - Assertions.assertNotNull(h); - Assertions.assertSame(def, h); - - h = matcher.lookup("default/request"); - Assertions.assertNotNull(h); - Assertions.assertSame(def, h); - } - - @Test - public void testWildCardMatching2() throws Exception { - final Object h1 = new Object(); - final Object h2 = new Object(); - final Object def = new Object(); - - final LookupRegistry matcher = new UriPatternOrderedMatcher<>(); - matcher.register("*", def); - matcher.register("*.view", h1); - matcher.register("*.form", h2); - - Object h; - - h = matcher.lookup("/that.view"); - Assertions.assertNotNull(h); - Assertions.assertSame(def, h); - - h = matcher.lookup("/that.form"); - Assertions.assertNotNull(h); - Assertions.assertSame(def, h); - - h = matcher.lookup("/whatever"); - Assertions.assertNotNull(h); - Assertions.assertSame(def, h); - } - - @Test - public void testSuffixPatternOverPrefixPatternMatch() throws Exception { - final Object h1 = new Object(); - final Object h2 = new Object(); - - final LookupRegistry matcher = new UriPatternOrderedMatcher<>(); - matcher.register("/ma*", h1); - matcher.register("*tch", h2); - - final Object h = matcher.lookup("/match"); - Assertions.assertNotNull(h); - Assertions.assertSame(h1, h); - } - - @Test - public void testRegisterInvalidInput() throws Exception { - final LookupRegistry matcher = new UriPatternOrderedMatcher<>(); - Assertions.assertThrows(NullPointerException.class, () -> - matcher.register(null, null)); - } - - @Test - public void testLookupInvalidInput() throws Exception { - final LookupRegistry matcher = new UriPatternOrderedMatcher<>(); - Assertions.assertThrows(NullPointerException.class, () -> - matcher.lookup(null)); - } - - @Test - public void testMatchExact() { - final Object h1 = new Object(); - final Object h2 = new Object(); - - final LookupRegistry matcher = new UriPatternOrderedMatcher<>(); - matcher.register("exact", h1); - matcher.register("*", h2); - - final Object h = matcher.lookup("exact"); - Assertions.assertNotNull(h); - Assertions.assertSame(h1, h); - } - - @Test - public void testStarAndExact() { - final Object h1 = new Object(); - final Object h2 = new Object(); - - final LookupRegistry matcher = new UriPatternOrderedMatcher<>(); - matcher.register("*", h1); - matcher.register("exact", h2); - - final Object h = matcher.lookup("exact"); - Assertions.assertNotNull(h); - Assertions.assertSame(h1, h); - } - - @Test - public void testExactAndStar() { - final Object h1 = new Object(); - final Object h2 = new Object(); - - final LookupRegistry matcher = new UriPatternOrderedMatcher<>(); - matcher.register("exact", h1); - matcher.register("*", h2); - - final Object h = matcher.lookup("exact"); - Assertions.assertNotNull(h); - Assertions.assertSame(h1, h); - } -} diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestUriRegexMatcher.java b/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestUriRegexMatcher.java deleted file mode 100644 index f3745ef409..0000000000 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestUriRegexMatcher.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package org.apache.hc.core5.http.protocol; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class TestUriRegexMatcher { - - @Test - public void testRegisterUnregister() throws Exception { - final Object h1 = new Object(); - final Object h2 = new Object(); - final Object h3 = new Object(); - - final LookupRegistry matcher = new UriRegexMatcher<>(); - matcher.register("/h1", h1); - matcher.register("/h2", h2); - matcher.register("/h3", h3); - - Object h; - - h = matcher.lookup("/h1"); - Assertions.assertNotNull(h); - Assertions.assertSame(h1, h); - h = matcher.lookup("/h2"); - Assertions.assertNotNull(h); - Assertions.assertSame(h2, h); - h = matcher.lookup("/h3"); - Assertions.assertNotNull(h); - Assertions.assertSame(h3, h); - - matcher.unregister("/h1"); - h = matcher.lookup("/h1"); - Assertions.assertNull(h); - } - - @Test - public void testRegisterNull() throws Exception { - final LookupRegistry matcher = new UriRegexMatcher<>(); - Assertions.assertThrows(NullPointerException.class, () -> - matcher.register(null, null)); - } - - @Test - public void testWildCardMatching1a() throws Exception { - final Object h1 = new Object(); - final Object h2 = new Object(); - final Object h3 = new Object(); - final Object def = new Object(); - - final LookupRegistry matcher = new UriRegexMatcher<>(); - matcher.register(".*", def); - matcher.register("/one/.*", h1); - matcher.register("/one/two/.*", h2); - matcher.register("/one/two/three/.*", h3); - - Object h; - - h = matcher.lookup("/one/request"); - Assertions.assertNotNull(h); - Assertions.assertSame(def, h); - - h = matcher.lookup("/one/two/request"); - Assertions.assertNotNull(h); - Assertions.assertSame(def, h); - - h = matcher.lookup("/one/two/three/request"); - Assertions.assertNotNull(h); - Assertions.assertSame(def, h); - - h = matcher.lookup("default/request"); - Assertions.assertNotNull(h); - Assertions.assertSame(def, h); - } - - @Test - public void testWildCardMatching1b() throws Exception { - final Object h1 = new Object(); - final Object h2 = new Object(); - final Object h3 = new Object(); - final Object def = new Object(); - - final LookupRegistry matcher = new UriRegexMatcher<>(); - matcher.register("/one/two/three/.*", h3); - matcher.register("/one/two/.*", h2); - matcher.register("/one/.*", h1); - matcher.register(".*", def); - - Object h; - - h = matcher.lookup("/one/request"); - Assertions.assertNotNull(h); - Assertions.assertSame(h1, h); - - h = matcher.lookup("/one/two/request"); - Assertions.assertNotNull(h); - Assertions.assertSame(h2, h); - - h = matcher.lookup("/one/two/three/request"); - Assertions.assertNotNull(h); - Assertions.assertSame(h3, h); - - h = matcher.lookup("default/request"); - Assertions.assertNotNull(h); - Assertions.assertSame(def, h); - } - - @Test - public void testWildCardMatching2a() throws Exception { - final Object h1 = new Object(); - final Object h2 = new Object(); - final Object def = new Object(); - - final LookupRegistry matcher = new UriRegexMatcher<>(); - matcher.register(".*", def); - matcher.register(".*\\.view", h1); - matcher.register(".*\\.form", h2); - - Object h; - - h = matcher.lookup("/that.view"); - Assertions.assertNotNull(h); - Assertions.assertSame(def, h); - - h = matcher.lookup("/that.form"); - Assertions.assertNotNull(h); - Assertions.assertSame(def, h); - - h = matcher.lookup("/whatever"); - Assertions.assertNotNull(h); - Assertions.assertSame(def, h); - } - - @Test - public void testWildCardMatching2b() throws Exception { - final Object h1 = new Object(); - final Object h2 = new Object(); - final Object def = new Object(); - - final LookupRegistry matcher = new UriRegexMatcher<>(); - matcher.register(".*\\.form", h2); - matcher.register(".*\\.view", h1); - matcher.register(".*", def); - - Object h; - - h = matcher.lookup("/that.view"); - Assertions.assertNotNull(h); - Assertions.assertSame(h1, h); - - h = matcher.lookup("/that.form"); - Assertions.assertNotNull(h); - Assertions.assertSame(h2, h); - - h = matcher.lookup("/whatever"); - Assertions.assertNotNull(h); - Assertions.assertSame(def, h); - } - - @Test - public void testSuffixPatternOverPrefixPatternMatch() throws Exception { - final Object h1 = new Object(); - final Object h2 = new Object(); - - final LookupRegistry matcher = new UriRegexMatcher<>(); - matcher.register("/ma.*", h1); - matcher.register(".*tch", h2); - - final Object h = matcher.lookup("/match"); - Assertions.assertNotNull(h); - Assertions.assertSame(h1, h); - } - - @Test - public void testRegisterInvalidInput() throws Exception { - final LookupRegistry matcher = new UriRegexMatcher<>(); - Assertions.assertThrows(NullPointerException.class, () -> - matcher.register(null, null)); - } - - @Test - public void testLookupInvalidInput() throws Exception { - final LookupRegistry matcher = new UriRegexMatcher<>(); - Assertions.assertThrows(NullPointerException.class, () -> - matcher.lookup(null)); - } - -} diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/ViaRequestTest.java b/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/ViaRequestTest.java new file mode 100644 index 0000000000..2d31de51aa --- /dev/null +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/ViaRequestTest.java @@ -0,0 +1,117 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * 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); + + } +} \ No newline at end of file diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/ssl/TLSTest.java b/httpcore5/src/test/java/org/apache/hc/core5/http/ssl/TLSTest.java index e9a2730a46..4f5b3ef9e0 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/ssl/TLSTest.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/ssl/TLSTest.java @@ -27,10 +27,14 @@ 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; @@ -86,4 +90,32 @@ void excludeWeak() { 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?")); + } + } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/ssl/TestTlsCiphers.java b/httpcore5/src/test/java/org/apache/hc/core5/http/ssl/TestTlsCiphers.java index 8313b9e399..e510af2e9b 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/ssl/TestTlsCiphers.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/ssl/TestTlsCiphers.java @@ -33,10 +33,10 @@ /** * Unit tests for {@link TlsCiphers}. */ -public class TestTlsCiphers { +class TestTlsCiphers { @Test - public void testStrongCipherSuites() { + void testStrongCipherSuites() { final String[] strongCipherSuites = { "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_RSA_WITH_AES_256_CBC_SHA256", @@ -51,7 +51,7 @@ public void testStrongCipherSuites() { } @Test - public void testWeakCiphersDisabledByDefault() { + void testWeakCiphersDisabledByDefault() { final String[] weakCiphersSuites = { "SSL_RSA_WITH_RC4_128_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA", diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/ssl/TestTlsVersionParser.java b/httpcore5/src/test/java/org/apache/hc/core5/http/ssl/TestTlsVersionParser.java deleted file mode 100644 index 39d393bed9..0000000000 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/ssl/TestTlsVersionParser.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package org.apache.hc.core5.http.ssl; - -import static org.hamcrest.MatcherAssert.assertThat; - -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.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - * Unit tests for {@link TlsVersionParser}. - */ -public class TestTlsVersionParser { - - private TlsVersionParser impl; - - @BeforeEach - public void setup() { - impl = new TlsVersionParser(); - } - - @Test - public void testParseBasic() throws Exception { - assertThat(impl.parse("TLSv1"), CoreMatchers.equalTo(TLS.V_1_0.getVersion())); - assertThat(impl.parse("TLSv1.1"), CoreMatchers.equalTo(TLS.V_1_1.getVersion())); - assertThat(impl.parse("TLSv1.2"), CoreMatchers.equalTo(TLS.V_1_2.getVersion())); - assertThat(impl.parse("TLSv1.3"), CoreMatchers.equalTo(TLS.V_1_3.getVersion())); - assertThat(impl.parse("TLSv22.356"), CoreMatchers.equalTo(new ProtocolVersion("TLS", 22, 356))); - } - - @Test - public void testParseBuffer() throws Exception { - final Tokenizer.Cursor cursor = new Tokenizer.Cursor(1, 13); - assertThat(impl.parse(" TLSv1.2,0000", cursor, Tokenizer.INIT_BITSET(',')), - CoreMatchers.equalTo(TLS.V_1_2.getVersion())); - assertThat(cursor.getPos(), CoreMatchers.equalTo(8)); - } - - @Test - public void testParseFailure1() throws Exception { - Assertions.assertThrows(ParseException.class, () -> - impl.parse("Tlsv1")); - } - - @Test - public void testParseFailure2() throws Exception { - Assertions.assertThrows(ParseException.class, () -> - impl.parse("TLSV1")); - } - - @Test - public void testParseFailure3() throws Exception { - Assertions.assertThrows(ParseException.class, () -> - impl.parse("TLSv")); - } - - @Test - public void testParseFailure4() throws Exception { - Assertions.assertThrows(ParseException.class, () -> - impl.parse("TLSv1A")); - } - - @Test - public void testParseFailure5() throws Exception { - Assertions.assertThrows(ParseException.class, () -> - impl.parse("TLSv1.A")); - } - -} diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/support/TestBasicMessageBuilders.java b/httpcore5/src/test/java/org/apache/hc/core5/http/support/TestBasicMessageBuilders.java index 9c91c8ec66..a25d567518 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/support/TestBasicMessageBuilders.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/support/TestBasicMessageBuilders.java @@ -28,6 +28,7 @@ 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; @@ -50,10 +51,10 @@ /** * Simple tests for {@link BasicResponseBuilder} and {@link BasicRequestBuilder}. */ -public class TestBasicMessageBuilders { +class TestBasicMessageBuilders { @Test - public void testResponseBasics() throws Exception { + void testResponseBasics() { final BasicResponseBuilder builder = BasicResponseBuilder.create(200); Assertions.assertEquals(200, builder.getStatus()); Assertions.assertNull(builder.getHeaders()); @@ -111,7 +112,7 @@ public void testResponseBasics() throws Exception { } @Test - public void testRequestBasics() throws Exception { + void testRequestBasics() throws Exception { final BasicRequestBuilder builder = BasicRequestBuilder.get(); Assertions.assertEquals(URI.create("/"), builder.getUri()); Assertions.assertEquals("GET", builder.getMethod()); @@ -205,7 +206,7 @@ public void testRequestBasics() throws Exception { } @Test - public void testResponseCopy() throws Exception { + void testResponseCopy() { final HttpResponse response = new BasicHttpResponse(400); response.addHeader("h1", "v1"); response.addHeader("h1", "v2"); @@ -220,7 +221,7 @@ public void testResponseCopy() throws Exception { } @Test - public void testRequestCopy() throws Exception { + void testRequestCopy() { final HttpRequest request = new BasicHttpRequest(Method.GET, URI.create("https://host:3456/stuff?blah")) ; request.addHeader("h1", "v1"); request.addHeader("h1", "v2"); @@ -237,4 +238,11 @@ public void testRequestCopy() throws Exception { 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()); + } + } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/support/TestExpectSupport.java b/httpcore5/src/test/java/org/apache/hc/core5/http/support/TestExpectSupport.java new file mode 100644 index 0000000000..2177a4d7b0 --- /dev/null +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/support/TestExpectSupport.java @@ -0,0 +1,110 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * 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))); + } + +} diff --git a/httpcore5/src/test/java/org/apache/hc/core5/io/TestSocketSupport.java b/httpcore5/src/test/java/org/apache/hc/core5/io/TestSocketSupport.java new file mode 100644 index 0000000000..00bf41a646 --- /dev/null +++ b/httpcore5/src/test/java/org/apache/hc/core5/io/TestSocketSupport.java @@ -0,0 +1,108 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by 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.net.ServerSocket; +import java.net.Socket; +import java.net.SocketOption; + +import javax.net.ServerSocketFactory; + +import org.apache.hc.core5.util.ReflectionUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class TestSocketSupport { + + @Test + public void testGetExtendedSocketOptionOrNull() { + testGetExtendedSocketOption(SocketSupport.TCP_KEEPIDLE); + testGetExtendedSocketOption(SocketSupport.TCP_KEEPINTERVAL); + testGetExtendedSocketOption(SocketSupport.TCP_KEEPCOUNT); + } + + private void testGetExtendedSocketOption(final String option) { + final SocketOption socketOption = SocketSupport.getExtendedSocketOptionOrNull(option); + // 1.Partial versions of jdk1.8 contain TCP_KEEPIDLE, TCP_KEEPINTERVAL, TCP_KEEPCOUNT. + // 2. Windows may not support TCP_KEEPIDLE, TCP_KEEPINTERVAL, TCP_KEEPCOUNT. + if (ReflectionUtils.determineJRELevel() > 8 && !isWindows()) { + Assertions.assertNotNull(socketOption); + } + } + + @Test + public void testSetOption() throws IOException { + if (ReflectionUtils.determineJRELevel() > 8 && isWindows() == false) { + { + // test Socket + final Socket sock = new Socket(); + SocketSupport.setOption(sock, SocketSupport.TCP_KEEPIDLE, 20); + SocketSupport.setOption(sock, SocketSupport.TCP_KEEPINTERVAL, 21); + SocketSupport.setOption(sock, SocketSupport.TCP_KEEPCOUNT, 22); + + final SocketOption tcpKeepIdle = SocketSupport.getExtendedSocketOptionOrNull(SocketSupport.TCP_KEEPIDLE); + assert tcpKeepIdle != null; + Assertions.assertEquals(20, ReflectionUtils.callGetter(sock, "Option", tcpKeepIdle, SocketOption.class, Integer.class)); + + final SocketOption tcpKeepInterval = SocketSupport.getExtendedSocketOptionOrNull(SocketSupport.TCP_KEEPINTERVAL); + assert tcpKeepInterval != null; + Assertions.assertEquals(21, ReflectionUtils.callGetter(sock, "Option", tcpKeepInterval, SocketOption.class, Integer.class)); + + final SocketOption tcpKeepCount = SocketSupport.getExtendedSocketOptionOrNull(SocketSupport.TCP_KEEPCOUNT); + assert tcpKeepCount != null; + Assertions.assertEquals(22, ReflectionUtils.callGetter(sock, "Option", tcpKeepCount, SocketOption.class, Integer.class)); + } + + { + // test ServerSocket + final ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(); + SocketSupport.setOption(serverSocket, SocketSupport.TCP_KEEPIDLE, 20); + SocketSupport.setOption(serverSocket, SocketSupport.TCP_KEEPINTERVAL, 21); + SocketSupport.setOption(serverSocket, SocketSupport.TCP_KEEPCOUNT, 22); + + final SocketOption tcpKeepIdle = SocketSupport.getExtendedSocketOptionOrNull(SocketSupport.TCP_KEEPIDLE); + assert tcpKeepIdle != null; + Assertions.assertEquals(20, ReflectionUtils.callGetter(serverSocket, "Option", tcpKeepIdle, SocketOption.class, Integer.class)); + + final SocketOption tcpKeepInterval = SocketSupport.getExtendedSocketOptionOrNull(SocketSupport.TCP_KEEPINTERVAL); + assert tcpKeepInterval != null; + Assertions.assertEquals(21, ReflectionUtils.callGetter(serverSocket, "Option", tcpKeepInterval, SocketOption.class, Integer.class)); + + final SocketOption tcpKeepCount = SocketSupport.getExtendedSocketOptionOrNull(SocketSupport.TCP_KEEPCOUNT); + assert tcpKeepCount != null; + Assertions.assertEquals(22, ReflectionUtils.callGetter(serverSocket, "Option", tcpKeepCount, SocketOption.class, Integer.class)); + } + } + } + + public static boolean isWindows() { + return System.getProperty("os.name").contains("Windows"); + } + +} diff --git a/httpcore5/src/test/java/org/apache/hc/core5/net/TestHost.java b/httpcore5/src/test/java/org/apache/hc/core5/net/TestHost.java index d2090923aa..96877cd3ff 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/net/TestHost.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/net/TestHost.java @@ -40,34 +40,43 @@ * Unit tests for {@link Host}. * */ -public class TestHost { +class TestHost { @Test - public void testConstructor() { + 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 - public void testHashCode() throws Exception { + 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.assertTrue(host1.hashCode() != host2.hashCode()); - Assertions.assertTrue(host1.hashCode() != host3.hashCode()); + Assertions.assertNotEquals(host1.hashCode(), host2.hashCode()); + Assertions.assertNotEquals(host1.hashCode(), host3.hashCode()); Assertions.assertEquals(host2.hashCode(), host4.hashCode()); } @Test - public void testEquals() throws Exception { + void testEquals() { final Host host1 = new Host("somehost", 8080); final Host host2 = new Host("somehost", 80); final Host host3 = new Host("someotherhost", 8080); @@ -80,18 +89,18 @@ public void testEquals() throws Exception { } @Test - public void testToString() throws Exception { + void testToString() { final Host host1 = new Host("somehost", 8888); Assertions.assertEquals("somehost:8888", host1.toString()); } @Test - public void testSerialization() throws Exception { + void testSerialization() throws Exception { final Host orig = new Host("somehost", 8080); final ByteArrayOutputStream outbuffer = new ByteArrayOutputStream(); - final ObjectOutputStream outStream = new ObjectOutputStream(outbuffer); - outStream.writeObject(orig); - outStream.close(); + 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); @@ -100,39 +109,41 @@ public void testSerialization() throws Exception { } @Test - public void testCreateFromString() throws Exception { + 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 - public void testCreateFromStringInvalid() throws Exception { + void testCreateFromStringInvalid() { Assertions.assertThrows(URISyntaxException.class, () -> Host.create(" host ")); Assertions.assertThrows(URISyntaxException.class, () -> Host.create("host :8080")); Assertions.assertThrows(IllegalArgumentException.class, () -> Host.create("")); } @Test - public void testIpv6HostAndPort() throws Exception { + void testIpv6HostAndPort() throws Exception { final Host host = Host.create("[::1]:80"); Assertions.assertEquals("::1", host.getHostName()); Assertions.assertEquals(80, host.getPort()); } @Test - public void testIpv6HostAndPortWithoutBrackets() { + void testIpv6HostAndPortWithoutBrackets() { // ambiguous Assertions.assertThrows(URISyntaxException.class, () -> Host.create("::1:80")); } @Test - public void testIpv6HostWithoutPort() { + void testIpv6HostWithoutPort() { Assertions.assertThrows(URISyntaxException.class, () -> Host.create("::1")); } @Test - public void testIpv6HostToString() { + void testIpv6HostToString() { Assertions.assertEquals("[::1]:80", new Host("::1", 80).toString()); Assertions.assertEquals("[::1]", new Host("::1", -1).toString()); } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/net/TestInetAddressUtils.java b/httpcore5/src/test/java/org/apache/hc/core5/net/TestInetAddressUtils.java index 7e6aa99050..96fd69fff1 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/net/TestInetAddressUtils.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/net/TestInetAddressUtils.java @@ -33,122 +33,140 @@ /** * Unit tests for InetAddressUtils. */ -public class TestInetAddressUtils { +class TestInetAddressUtils { @Test - public void testValidIPv4Address() { - Assertions.assertTrue(InetAddressUtils.isIPv4Address("127.0.0.1")); - Assertions.assertTrue(InetAddressUtils.isIPv4Address("192.168.0.0")); - Assertions.assertTrue(InetAddressUtils.isIPv4Address("255.255.255.255")); + 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")); } @Test - public void testInvalidIPv4Address() { - Assertions.assertFalse(InetAddressUtils.isIPv4Address(" 127.0.0.1 ")); // Blanks not allowed - Assertions.assertFalse(InetAddressUtils.isIPv4Address("g.ar.ba.ge")); - Assertions.assertFalse(InetAddressUtils.isIPv4Address("192.168.0")); - Assertions.assertFalse(InetAddressUtils.isIPv4Address("256.255.255.255")); - Assertions.assertFalse(InetAddressUtils.isIPv4Address("0.168.0.0")); //IP address that starts with zero not allowed + 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")); + Assertions.assertFalse(InetAddressUtils.isIPv4("0.168.0.0")); //IP address that starts with zero not allowed } @Test - public void testValidIPv6Address() { - Assertions.assertTrue(InetAddressUtils.isIPv6StdAddress("2001:0db8:0000:0000:0000:0000:1428:57ab")); - Assertions.assertTrue(InetAddressUtils.isIPv6StdAddress("2001:db8:0:0:0:0:1428:57ab")); - Assertions.assertTrue(InetAddressUtils.isIPv6StdAddress("0:0:0:0:0:0:0:0")); - Assertions.assertTrue(InetAddressUtils.isIPv6StdAddress("0:0:0:0:0:0:0:1")); - - Assertions.assertTrue(InetAddressUtils.isIPv6HexCompressedAddress("2001:0db8:0:0::1428:57ab")); - Assertions.assertTrue(InetAddressUtils.isIPv6HexCompressedAddress("2001:0db8::1428:57ab")); - Assertions.assertTrue(InetAddressUtils.isIPv6HexCompressedAddress("2001:db8::1428:57ab")); - Assertions.assertTrue(InetAddressUtils.isIPv6HexCompressedAddress("::1")); - Assertions.assertTrue(InetAddressUtils.isIPv6HexCompressedAddress("::")); // http://tools.ietf.org/html/rfc4291#section-2.2 - - Assertions.assertTrue(InetAddressUtils.isIPv6Address("2001:0db8:0000:0000:0000:0000:1428:57ab")); - Assertions.assertTrue(InetAddressUtils.isIPv6Address("2001:db8:0:0:0:0:1428:57ab")); - Assertions.assertTrue(InetAddressUtils.isIPv6Address("0:0:0:0:0:0:0:0")); - Assertions.assertTrue(InetAddressUtils.isIPv6Address("0:0:0:0:0:0:0:1")); - Assertions.assertTrue(InetAddressUtils.isIPv6Address("2001:0db8:0:0::1428:57ab")); - Assertions.assertTrue(InetAddressUtils.isIPv6Address("2001:0db8::1428:57ab")); - Assertions.assertTrue(InetAddressUtils.isIPv6Address("2001:db8::1428:57ab")); - Assertions.assertTrue(InetAddressUtils.isIPv6Address("::1")); - Assertions.assertTrue(InetAddressUtils.isIPv6Address("::")); // http://tools.ietf.org/html/rfc4291#section-2.2 + 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 - public void testInvalidIPv6Address() { - Assertions.assertFalse(InetAddressUtils.isIPv6Address("2001:0db8:0000:garb:age0:0000:1428:57ab")); - Assertions.assertFalse(InetAddressUtils.isIPv6Address("2001:0gb8:0000:0000:0000:0000:1428:57ab")); - Assertions.assertFalse(InetAddressUtils.isIPv6StdAddress("0:0:0:0:0:0:0:0:0")); // Too many - Assertions.assertFalse(InetAddressUtils.isIPv6StdAddress("0:0:0:0:0:0:0")); // Too few - Assertions.assertFalse(InetAddressUtils.isIPv6HexCompressedAddress(":1")); - Assertions.assertFalse(InetAddressUtils.isIPv6Address(":1")); - Assertions.assertFalse(InetAddressUtils.isIPv6Address("2001:0db8::0000::57ab")); // Cannot have two contractions - Assertions.assertFalse(InetAddressUtils.isIPv6HexCompressedAddress("1:2:3:4:5:6:7::9")); // too many fields before :: - Assertions.assertFalse(InetAddressUtils.isIPv6HexCompressedAddress("1::3:4:5:6:7:8:9")); // too many fields after :: - Assertions.assertFalse(InetAddressUtils.isIPv6HexCompressedAddress("::3:4:5:6:7:8:9")); // too many fields after :: - Assertions.assertFalse(InetAddressUtils.isIPv6Address("")); // empty + 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 - public void testValidIPv6BracketAddress() { - Assertions.assertTrue(InetAddressUtils.isIPv6URLBracketedAddress("[2001:0db8:0000:0000:0000:0000:1428:57ab]")); - Assertions.assertTrue(InetAddressUtils.isIPv6URLBracketedAddress("[2001:db8:0:0:0:0:1428:57ab]")); - Assertions.assertTrue(InetAddressUtils.isIPv6URLBracketedAddress("[0:0:0:0:0:0:0:0]")); - Assertions.assertTrue(InetAddressUtils.isIPv6URLBracketedAddress("[0:0:0:0:0:0:0:1]")); - Assertions.assertTrue(InetAddressUtils.isIPv6URLBracketedAddress("[2001:0db8:0:0::1428:57ab]")); - Assertions.assertTrue(InetAddressUtils.isIPv6URLBracketedAddress("[2001:0db8::1428:57ab]")); - Assertions.assertTrue(InetAddressUtils.isIPv6URLBracketedAddress("[2001:db8::1428:57ab]")); - Assertions.assertTrue(InetAddressUtils.isIPv6URLBracketedAddress("[::1]")); + 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.isIPv6URLBracketedAddress("[::]")); + Assertions.assertTrue(InetAddressUtils.isIPv6URLBracketed("[::]")); } @Test - public void testInvalidIPv6BracketAddress() { - Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("2001:0db8:0000:garb:age0:0000:1428:57ab")); - Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[2001:0db8:0000:garb:age0:0000:1428:57ab]")); - Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("2001:0gb8:0000:0000:0000:0000:1428:57ab")); - Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[2001:0gb8:0000:0000:0000:0000:1428:57ab]")); + 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.isIPv6URLBracketedAddress("0:0:0:0:0:0:0:0:0")); - Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[0:0:0:0:0:0:0:0:0]")); + 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.isIPv6URLBracketedAddress("0:0:0:0:0:0:0")); - Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[0:0:0:0:0:0:0]")); - Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress(":1")); - Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[:1]")); + 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.isIPv6URLBracketedAddress("2001:0db8::0000::57ab")); - Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[2001:0db8::0000::57ab]")); + Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("2001:0db8::0000::57ab")); + Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("[2001:0db8::0000::57ab]")); // too many fields before :: - Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("1:2:3:4:5:6:7::9")); - Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[1:2:3:4:5:6:7::9]")); + 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.isIPv6URLBracketedAddress("1::3:4:5:6:7:8:9")); - Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[1::3:4:5:6:7:8:9]")); + 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.isIPv6URLBracketedAddress("::3:4:5:6:7:8:9")); - Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[::3:4:5:6:7:8:9]")); + 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.isIPv6URLBracketedAddress("")); - Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("[]")); + Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("")); + Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("[]")); // missing brackets - Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("::")); - Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("::1")); - Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketedAddress("2001:db8::1428:57ab")); + Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("::")); + Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("::1")); + Assertions.assertFalse(InetAddressUtils.isIPv6URLBracketed("2001:db8::1428:57ab")); } @Test // Test HTTPCLIENT-1319 - public void testInvalidIPv6AddressIncorrectGroupCount() { - Assertions.assertFalse(InetAddressUtils.isIPv6HexCompressedAddress("1:2::4:5:6:7:8:9")); // too many fields in total - Assertions.assertFalse(InetAddressUtils.isIPv6HexCompressedAddress("1:2:3:4:5:6::8:9")); // too many fields in total + 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 - public void testHasValidIPv6ColonCount() { + void testHasValidIPv6ColonCount() { Assertions.assertFalse(InetAddressUtils.hasValidIPv6ColonCount("")); Assertions.assertFalse(InetAddressUtils.hasValidIPv6ColonCount(":")); Assertions.assertFalse(InetAddressUtils.hasValidIPv6ColonCount("127.0.0.1")); @@ -169,15 +187,15 @@ public void testHasValidIPv6ColonCount() { } @Test - public void testValidIPv4MappedIPv6Address() { - Assertions.assertTrue(InetAddressUtils.isIPv4MappedIPv64Address("::FFFF:1.2.3.4")); - Assertions.assertTrue(InetAddressUtils.isIPv4MappedIPv64Address("::ffff:255.255.255.255")); + void testValidIPv4MappedIPv6Address() { + Assertions.assertTrue(InetAddressUtils.isIPv4MappedIPv6("::FFFF:1.2.3.4")); + Assertions.assertTrue(InetAddressUtils.isIPv4MappedIPv6("::ffff:255.255.255.255")); } @Test - public void testInValidIPv4MappedIPv6Address() { - Assertions.assertFalse(InetAddressUtils.isIPv4MappedIPv64Address("2001:0db8:0000:0000:0000:0000:1428:57ab")); - Assertions.assertFalse(InetAddressUtils.isIPv4MappedIPv64Address("::ffff:1:2:3:4")); + void testInValidIPv4MappedIPv6Address() { + Assertions.assertFalse(InetAddressUtils.isIPv4MappedIPv6("2001:0db8:0000:0000:0000:0000:1428:57ab")); + Assertions.assertFalse(InetAddressUtils.isIPv4MappedIPv6("::ffff:1:2:3:4")); } } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/net/TestPercentCodec.java b/httpcore5/src/test/java/org/apache/hc/core5/net/TestPercentCodec.java index 99fe9a0508..b72e1583dd 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/net/TestPercentCodec.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/net/TestPercentCodec.java @@ -28,19 +28,23 @@ 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}. */ -public class TestPercentCodec { +class TestPercentCodec { @Test - public void testCoding() { + void testCoding() { final StringBuilder buf = new StringBuilder(); PercentCodec.encode(buf, "blah!", StandardCharsets.UTF_8); PercentCodec.encode(buf, " ~ ", StandardCharsets.UTF_8); @@ -49,7 +53,7 @@ public void testCoding() { } @Test - public void testDecoding() { + 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), @@ -59,7 +63,7 @@ public void testDecoding() { } @Test - public void testDecodingPartialContent() { + void testDecodingPartialContent() { assertThat(PercentCodec.decode("blah%21%20%", StandardCharsets.UTF_8), CoreMatchers.equalTo("blah! %")); assertThat(PercentCodec.decode("blah%21%20%a", StandardCharsets.UTF_8), @@ -68,4 +72,30 @@ public void testDecodingPartialContent() { 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)); + } + } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/net/TestURIAuthority.java b/httpcore5/src/test/java/org/apache/hc/core5/net/TestURIAuthority.java index e32e7a2d0c..07b4f09362 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/net/TestURIAuthority.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/net/TestURIAuthority.java @@ -43,10 +43,10 @@ * Unit tests for {@link URIAuthority}. * */ -public class TestURIAuthority { +class TestURIAuthority { @Test - public void testConstructor() { + void testConstructor() { final URIAuthority host1 = new URIAuthority("somehost"); Assertions.assertEquals("somehost", host1.getHostName()); Assertions.assertEquals(-1, host1.getPort()); @@ -59,7 +59,7 @@ public void testConstructor() { } @Test - public void testHashCode() throws Exception { + void testHashCode() { final URIAuthority host1 = new URIAuthority("somehost", 8080); final URIAuthority host2 = new URIAuthority("somehost", 80); final URIAuthority host3 = new URIAuthority("someotherhost", 8080); @@ -69,16 +69,16 @@ public void testHashCode() throws Exception { final URIAuthority host7 = new URIAuthority("user", "somehost", 80); Assertions.assertEquals(host1.hashCode(), host1.hashCode()); - Assertions.assertTrue(host1.hashCode() != host2.hashCode()); - Assertions.assertTrue(host1.hashCode() != host3.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.assertTrue(host5.hashCode() != host6.hashCode()); + Assertions.assertNotEquals(host5.hashCode(), host6.hashCode()); Assertions.assertEquals(host6.hashCode(), host7.hashCode()); } @Test - public void testEquals() throws Exception { + void testEquals() { final URIAuthority host1 = new URIAuthority("somehost", 8080); final URIAuthority host2 = new URIAuthority("somehost", 80); final URIAuthority host3 = new URIAuthority("someotherhost", 8080); @@ -97,7 +97,7 @@ public void testEquals() throws Exception { } @Test - public void testToString() throws Exception { + void testToString() { final URIAuthority host1 = new URIAuthority("somehost"); Assertions.assertEquals("somehost", host1.toString()); final URIAuthority host2 = new URIAuthority("somehost", -1); @@ -107,12 +107,12 @@ public void testToString() throws Exception { } @Test - public void testSerialization() throws Exception { + void testSerialization() throws Exception { final URIAuthority orig = new URIAuthority("somehost", 8080); final ByteArrayOutputStream outbuffer = new ByteArrayOutputStream(); - final ObjectOutputStream outStream = new ObjectOutputStream(outbuffer); - outStream.writeObject(orig); - outStream.close(); + 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); @@ -121,7 +121,7 @@ public void testSerialization() throws Exception { } @Test - public void testParse() throws Exception { + void testParse() throws Exception { assertThat(URIAuthority.parse("somehost"), CoreMatchers.equalTo(new URIAuthority("somehost", -1))); assertThat(URIAuthority.parse("somehost/blah"), @@ -182,7 +182,7 @@ public void testParse() throws Exception { } @Test - public void testCreateFromString() throws Exception { + 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")); @@ -195,7 +195,7 @@ public void testCreateFromString() throws Exception { } @Test - public void testCreateFromIPv6String() throws Exception { + 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")); @@ -204,7 +204,7 @@ public void testCreateFromIPv6String() throws Exception { } @Test - public void testIpv6HostToString() { + 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()); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/net/TestURIBuilder.java b/httpcore5/src/test/java/org/apache/hc/core5/net/TestURIBuilder.java index e4f3dcc285..45dc8229dc 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/net/TestURIBuilder.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/net/TestURIBuilder.java @@ -30,6 +30,7 @@ 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; @@ -46,7 +47,8 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestURIBuilder { +@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"; @@ -56,7 +58,7 @@ static List parsePath(final CharSequence s) { } @Test - public void testParseSegments() throws Exception { + 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"))); @@ -76,7 +78,7 @@ static String formatPath(final String... pathSegments) { } @Test - public void testFormatSegments() throws Exception { + 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"), @@ -92,7 +94,7 @@ static List parseQuery(final CharSequence s) { } @Test - public void testParseQuery() throws Exception { + void testParseQuery() { assertThat(parseQuery(""), NameValuePairListMatcher.isEmpty()); assertThat(parseQuery("Name0"), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name0", null))); @@ -139,7 +141,7 @@ static String formatQuery(final NameValuePair... params) { } @Test - public void testFormatQuery() throws Exception { + 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=")); @@ -167,7 +169,7 @@ public void testFormatQuery() throws Exception { } @Test - public void testHierarchicalUri() throws Exception { + void testHierarchicalUri() throws Exception { final URI uri = new URI("http", "stuff", "localhost", 80, "/some stuff", "param=stuff", "fragment"); final URIBuilder uribuilder = new URIBuilder(uri); final URI result = uribuilder.build(); @@ -175,36 +177,28 @@ public void testHierarchicalUri() throws Exception { } @Test - public void testMutationToRelativeUri() throws Exception { - final URI uri = new URI("http://stuff@localhost:80/stuff?param=stuff#fragment"); - final URIBuilder uribuilder = new URIBuilder(uri).setHost((String) null); - final URI result = uribuilder.build(); - Assertions.assertEquals(new URI("http:///stuff?param=stuff#fragment"), result); - } - - @Test - public void testMutationRemoveFragment() throws Exception { + 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 - public void testMutationRemoveUserInfo() throws Exception { + 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 - public void testMutationRemovePort() throws Exception { + 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 - public void testOpaqueUri() throws Exception { + void testOpaqueUri() throws Exception { final URI uri = new URI("stuff", "some-stuff", "fragment"); final URIBuilder uribuilder = new URIBuilder(uri); final URI result = uribuilder.build(); @@ -212,20 +206,20 @@ public void testOpaqueUri() throws Exception { } @Test - public void testOpaqueUriMutation() throws Exception { + 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 - public void testHierarchicalUriMutation() throws Exception { + 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 - public void testLocalhost() throws Exception { + 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(); @@ -249,10 +243,10 @@ public void testLocalhost() throws Exception { Assertions.assertEquals(uri.getQuery(), bld.getQuery()); Assertions.assertEquals(uri.getFragment(), bld.getFragment()); - } + } @Test - public void testLoopbackAddress() throws Exception { + 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(); @@ -276,23 +270,23 @@ public void testLoopbackAddress() throws Exception { Assertions.assertEquals(uri.getQuery(), bld.getQuery()); Assertions.assertEquals(uri.getFragment(), bld.getFragment()); - } + } @Test - public void testEmpty() throws Exception { + void testEmpty() throws Exception { final URIBuilder uribuilder = new URIBuilder(); final URI result = uribuilder.build(); Assertions.assertEquals(new URI(""), result); } @Test - public void testEmptyPath() throws Exception { + void testEmptyPath() throws Exception { final URIBuilder uribuilder = new URIBuilder("http://thathost"); Assertions.assertTrue(uribuilder.isPathEmpty()); } @Test - public void testRemoveParameter() throws Exception { + 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()); @@ -317,7 +311,7 @@ public void testRemoveParameter() throws Exception { } @Test - public void testRemoveQuery() throws Exception { + 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(); @@ -325,7 +319,7 @@ public void testRemoveQuery() throws Exception { } @Test - public void testSetAuthorityFromNamedEndpointHost() throws Exception { + void testSetAuthorityFromNamedEndpointHost() throws Exception { final Host host = Host.create("localhost:88"); final URIBuilder uribuilder = new URIBuilder().setScheme(URIScheme.HTTP.id).setAuthority(host); // Check builder @@ -340,7 +334,7 @@ public void testSetAuthorityFromNamedEndpointHost() throws Exception { } @Test - public void testSetAuthorityFromNamedEndpointHttpHost() throws Exception { + void testSetAuthorityFromNamedEndpointHttpHost() throws Exception { final HttpHost httpHost = HttpHost.create("localhost:88"); final URIBuilder uribuilder = new URIBuilder().setScheme(URIScheme.HTTP.id).setAuthority(httpHost); // Check builder @@ -355,7 +349,7 @@ public void testSetAuthorityFromNamedEndpointHttpHost() throws Exception { } @Test - public void testSetAuthorityFromURIAuthority() throws Exception { + 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 @@ -372,7 +366,7 @@ public void testSetAuthorityFromURIAuthority() throws Exception { } @Test - public void testSetParameter() throws Exception { + 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") @@ -382,7 +376,7 @@ public void testSetParameter() throws Exception { } @Test - public void testGetFirstNamedParameter() throws Exception { + 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"); @@ -392,10 +386,12 @@ public void testGetFirstNamedParameter() throws Exception { // 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 - public void testSetParametersWithEmptyArrayArg() throws Exception { + 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(); @@ -403,7 +399,7 @@ public void testSetParametersWithEmptyArrayArg() throws Exception { } @Test - public void testSetParametersWithNullArrayArg() throws Exception { + 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(); @@ -411,7 +407,7 @@ public void testSetParametersWithNullArrayArg() throws Exception { } @Test - public void testSetParametersWithEmptyList() throws Exception { + 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(); @@ -419,7 +415,7 @@ public void testSetParametersWithEmptyList() throws Exception { } @Test - public void testSetParametersWithNullList() throws Exception { + 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(); @@ -427,7 +423,7 @@ public void testSetParametersWithNullList() throws Exception { } @Test - public void testParameterWithSpecialChar() throws Exception { + 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"); @@ -437,7 +433,7 @@ public void testParameterWithSpecialChar() throws Exception { } @Test - public void testAddParameter() throws Exception { + 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"); @@ -447,7 +443,7 @@ public void testAddParameter() throws Exception { } @Test - public void testQueryEncoding() throws Exception { + 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") @@ -457,7 +453,7 @@ public void testQueryEncoding() throws Exception { } @Test - public void testQueryAndParameterEncoding() throws Exception { + 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") @@ -467,7 +463,7 @@ public void testQueryAndParameterEncoding() throws Exception { } @Test - public void testPathEncoding() throws Exception { + void testPathEncoding() throws Exception { final URI uri1 = new URI("https://somehost.com/some%20path%20with%20blanks/"); final URI uri2 = new URIBuilder() .setScheme("https") @@ -478,7 +474,7 @@ public void testPathEncoding() throws Exception { } @Test - public void testAgainstURI() throws Exception { + 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"; @@ -507,16 +503,16 @@ public void testAgainstURI() throws Exception { } @Test - public void testBuildAddParametersUTF8() throws Exception { + void testBuildAddParametersUTF8() throws Exception { assertAddParameters(StandardCharsets.UTF_8); } @Test - public void testBuildAddParametersISO88591() throws Exception { + void testBuildAddParametersISO88591() throws Exception { assertAddParameters(StandardCharsets.ISO_8859_1); } - public void assertAddParameters(final Charset charset) throws Exception { + void assertAddParameters(final Charset charset) throws Exception { final URI uri = new URIBuilder("https://somehost.com/stuff") .setCharset(charset) .addParameters(createParameterList()).build(); @@ -531,16 +527,16 @@ public void assertAddParameters(final Charset charset) throws Exception { } @Test - public void testBuildSetParametersUTF8() throws Exception { + void testBuildSetParametersUTF8() throws Exception { assertSetParameters(StandardCharsets.UTF_8); } @Test - public void testBuildSetParametersISO88591() throws Exception { + void testBuildSetParametersISO88591() throws Exception { assertSetParameters(StandardCharsets.ISO_8859_1); } - public void assertSetParameters(final Charset charset) throws Exception { + void assertSetParameters(final Charset charset) throws Exception { final URI uri = new URIBuilder("https://somehost.com/stuff") .setCharset(charset) .setParameters(createParameterList()).build(); @@ -548,7 +544,7 @@ public void assertSetParameters(final Charset charset) throws Exception { assertBuild(charset, uri); } - public void assertBuild(final Charset charset, final URI uri) throws Exception { + 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); @@ -566,26 +562,26 @@ private List createParameterList() { } @Test - public void testMalformedPath() throws Exception { + 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 - public void testRelativePath() throws Exception { + void testRelativePath() throws Exception { final URI uri = new URIBuilder("./mypath").build(); Assertions.assertEquals(new URI("./mypath"), uri); } @Test - public void testRelativePathWithAuthority() throws Exception { + void testRelativePathWithAuthority() throws Exception { final URI uri = new URIBuilder("./mypath").setHost("somehost").setScheme("http").build(); Assertions.assertEquals(new URI("http://somehost/./mypath"), uri); } @Test - public void testTolerateNullInput() throws Exception { + void testTolerateNullInput() throws Exception { assertThat(new URIBuilder() .setScheme(null) .setHost("localhost") @@ -599,7 +595,7 @@ public void testTolerateNullInput() throws Exception { } @Test - public void testTolerateBlankInput() throws Exception { + void testTolerateBlankInput() throws Exception { assertThat(new URIBuilder() .setScheme("") .setHost("localhost") @@ -614,7 +610,7 @@ public void testTolerateBlankInput() throws Exception { } @Test - public void testHttpHost() throws Exception { + void testHttpHost() throws Exception { final HttpHost httpHost = new HttpHost("http", "example.com", 1234); final URIBuilder uribuilder = new URIBuilder(); uribuilder.setHttpHost(httpHost); @@ -622,21 +618,21 @@ public void testHttpHost() throws Exception { } @Test - public void testSetHostWithReservedChars() throws Exception { + 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 - public void testGetHostWithReservedChars() throws Exception { + 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 - public void testMultipleLeadingPathSlashes() throws Exception { + void testMultipleLeadingPathSlashes() throws Exception { final URI uri = new URIBuilder() .setScheme("ftp") .setHost("somehost") @@ -646,7 +642,7 @@ public void testMultipleLeadingPathSlashes() throws Exception { } @Test - public void testNoAuthorityAndPath() throws Exception { + void testNoAuthorityAndPath() throws Exception { final URI uri = new URIBuilder() .setScheme("file") .setPath("/blah") @@ -655,7 +651,7 @@ public void testNoAuthorityAndPath() throws Exception { } @Test - public void testSetPathSegmentList() throws Exception { + void testSetPathSegmentList() throws Exception { final URI uri = new URIBuilder() .setScheme("https") .setHost("somehost") @@ -665,7 +661,7 @@ public void testSetPathSegmentList() throws Exception { } @Test - public void testSetPathSegmentsVarargs() throws Exception { + void testSetPathSegmentsVarargs() throws Exception { final URI uri = new URIBuilder() .setScheme("https") .setHost("somehost") @@ -675,7 +671,7 @@ public void testSetPathSegmentsVarargs() throws Exception { } @Test - public void testSetPathSegmentsRootlessList() throws Exception { + void testSetPathSegmentsRootlessList() throws Exception { final URI uri = new URIBuilder() .setScheme("file") .setPathSegmentsRootless(Arrays.asList("dir", "foo")) @@ -684,7 +680,7 @@ public void testSetPathSegmentsRootlessList() throws Exception { } @Test - public void testSetPathSegmentsRootlessVarargs() throws Exception { + void testSetPathSegmentsRootlessVarargs() throws Exception { final URI uri = new URIBuilder() .setScheme("file") .setPathSegmentsRootless("dir", "foo") @@ -693,7 +689,7 @@ public void testSetPathSegmentsRootlessVarargs() throws Exception { } @Test - public void testAppendToExistingPath() throws Exception { + void testAppendToExistingPath() throws Exception { final URI uri = new URIBuilder() .setScheme("https") .setHost("somehost") @@ -705,7 +701,7 @@ public void testAppendToExistingPath() throws Exception { } @Test - public void testAppendToNonExistingPath() throws Exception { + void testAppendToNonExistingPath() throws Exception { final URI uri = new URIBuilder() .setScheme("https") .setHost("somehost") @@ -716,7 +712,7 @@ public void testAppendToNonExistingPath() throws Exception { } @Test - public void testAppendNullToExistingPath() throws Exception { + void testAppendNullToExistingPath() throws Exception { final URI uri = new URIBuilder() .setScheme("https") .setHost("somehost") @@ -727,7 +723,7 @@ public void testAppendNullToExistingPath() throws Exception { } @Test - public void testAppendNullToNonExistingPath() throws Exception { + void testAppendNullToNonExistingPath() throws Exception { final URI uri = new URIBuilder() .setScheme("https") .setHost("somehost") @@ -737,7 +733,7 @@ public void testAppendNullToNonExistingPath() throws Exception { } @Test - public void testAppendSegmentsVarargsToExistingPath() throws Exception { + void testAppendSegmentsVarargsToExistingPath() throws Exception { final URI uri = new URIBuilder() .setScheme("https") .setHost("myhost") @@ -749,7 +745,7 @@ public void testAppendSegmentsVarargsToExistingPath() throws Exception { } @Test - public void testAppendSegmentsVarargsToNonExistingPath() throws Exception { + void testAppendSegmentsVarargsToNonExistingPath() throws Exception { final URI uri = new URIBuilder() .setScheme("https") .setHost("somehost") @@ -760,7 +756,7 @@ public void testAppendSegmentsVarargsToNonExistingPath() throws Exception { } @Test - public void testAppendNullSegmentsVarargs() throws Exception { + void testAppendNullSegmentsVarargs() throws Exception { final String pathSegment = null; final URI uri = new URIBuilder() .setScheme("https") @@ -771,7 +767,7 @@ public void testAppendNullSegmentsVarargs() throws Exception { } @Test - public void testAppendSegmentsListToExistingPath() throws Exception { + void testAppendSegmentsListToExistingPath() throws Exception { final URI uri = new URIBuilder() .setScheme("http") .setHost("myhost") @@ -782,7 +778,7 @@ public void testAppendSegmentsListToExistingPath() throws Exception { } @Test - public void testAppendSegmentsListToNonExistingPath() throws Exception { + void testAppendSegmentsListToNonExistingPath() throws Exception { final URI uri = new URIBuilder() .setScheme("http") .setHost("myhost") @@ -792,7 +788,7 @@ public void testAppendSegmentsListToNonExistingPath() throws Exception { } @Test - public void testAppendNullSegmentsList() throws Exception { + void testAppendNullSegmentsList() throws Exception { final List pathSegments = null; final URI uri = new URIBuilder() .setScheme("http") @@ -803,7 +799,7 @@ public void testAppendNullSegmentsList() throws Exception { } @Test - public void testNoAuthorityAndPathSegments() throws Exception { + void testNoAuthorityAndPathSegments() throws Exception { final URI uri = new URIBuilder() .setScheme("file") .setPathSegments("this", "that") @@ -812,7 +808,7 @@ public void testNoAuthorityAndPathSegments() throws Exception { } @Test - public void testNoAuthorityAndRootlessPath() throws Exception { + void testNoAuthorityAndRootlessPath() throws Exception { final URI uri = new URIBuilder() .setScheme("file") .setPath("blah") @@ -821,7 +817,7 @@ public void testNoAuthorityAndRootlessPath() throws Exception { } @Test - public void testNoAuthorityAndRootlessPathSegments() throws Exception { + void testNoAuthorityAndRootlessPathSegments() throws Exception { final URI uri = new URIBuilder() .setScheme("file") .setPathSegmentsRootless("this", "that") @@ -830,14 +826,14 @@ public void testNoAuthorityAndRootlessPathSegments() throws Exception { } @Test - public void testOpaque() throws Exception { + 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 - public void testAddParameterEncodingEquivalence() throws Exception { + 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( @@ -847,21 +843,21 @@ public void testAddParameterEncodingEquivalence() throws Exception { } @Test - public void testSchemeSpecificPartParametersNull() throws Exception { + 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 - public void testSchemeSpecificPartSetGet() throws Exception { + 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 - public void testSchemeSpecificPartNameValuePairByRFC6068Sample() throws Exception { + 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(); @@ -871,7 +867,7 @@ public void testSchemeSpecificPartNameValuePairByRFC6068Sample() throws Exceptio /** Common use case: mailto: scheme. See https://tools.ietf.org/html/rfc6068#section-2 */ @Test - public void testSchemeSpecificPartNameValuePairListByRFC6068Sample() throws Exception { + void testSchemeSpecificPartNameValuePairListByRFC6068Sample() throws Exception { final List parameters = new ArrayList<>(); parameters.add(new BasicNameValuePair("subject", "mail subject")); @@ -882,35 +878,34 @@ public void testSchemeSpecificPartNameValuePairListByRFC6068Sample() throws Exce } @Test - public void testNormalizeSyntax() throws Exception { + void testOptimize() throws Exception { Assertions.assertEquals("example://a/b/c/%7Bfoo%7D", - new URIBuilder("eXAMPLE://a/./b/../b/%63/%7bfoo%7d").normalizeSyntax().build().toASCIIString()); + 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").normalizeSyntax().build().toASCIIString()); + new URIBuilder("http://www.example.com/%3c").optimize().build().toASCIIString()); Assertions.assertEquals("http://www.example.com/", - new URIBuilder("HTTP://www.EXAMPLE.com/").normalizeSyntax().build().toASCIIString()); + 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").normalizeSyntax().build().toASCIIString()); + 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").normalizeSyntax().build().toASCIIString()); + 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").normalizeSyntax().build().toASCIIString()); + 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").normalizeSyntax().build().toASCIIString()); + 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/").normalizeSyntax().build().toASCIIString()); + new URIBuilder("http://www.example.com/blah-%28%20-blah-%20&%20-blah-%20)-blah/").optimize().build().toASCIIString()); Assertions.assertEquals("../../.././", - new URIBuilder("../../.././").normalizeSyntax().build().toASCIIString()); + new URIBuilder("../../.././").optimize().build().toASCIIString()); Assertions.assertEquals("file:../../.././", - new URIBuilder("file:../../.././").normalizeSyntax().build().toASCIIString()); + new URIBuilder("file:../../.././").optimize().build().toASCIIString()); Assertions.assertEquals("http://host/", - new URIBuilder("http://host/../../.././").normalizeSyntax().build().toASCIIString()); - Assertions.assertEquals("http:/", - new URIBuilder("http:///../../.././").normalizeSyntax().build().toASCIIString()); + new URIBuilder("http://host/../../.././").optimize().build().toASCIIString()); + Assertions.assertThrows(URISyntaxException.class, () -> new URIBuilder("http:///../../.././").optimize().build().toASCIIString()); } @Test - public void testIpv6Host() throws Exception { + void testIpv6Host() throws Exception { final URIBuilder builder = new URIBuilder("https://[::1]:432/path"); final URI uri = builder.build(); Assertions.assertEquals(432, builder.getPort()); @@ -924,7 +919,7 @@ public void testIpv6Host() throws Exception { } @Test - public void testIpv6HostWithPortUpdate() throws Exception { + 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); @@ -940,7 +935,7 @@ public void testIpv6HostWithPortUpdate() throws Exception { } @Test - public void testBuilderWithUnbracketedIpv6Host() throws Exception { + 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()); @@ -952,4 +947,30 @@ public void testBuilderWithUnbracketedIpv6Host() throws Exception { 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); + } } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/net/TestWWWFormCodec.java b/httpcore5/src/test/java/org/apache/hc/core5/net/TestWWWFormCodec.java index b5b77f9229..dc8e3682b6 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/net/TestWWWFormCodec.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/net/TestWWWFormCodec.java @@ -39,7 +39,7 @@ import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; -public class TestWWWFormCodec { +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"; @@ -49,7 +49,7 @@ private static List parse(final String params) { } @Test - public void testParse() throws Exception { + void testParse() { assertThat(parse(""), NameValuePairListMatcher.isEmpty()); assertThat(parse("Name0"), NameValuePairListMatcher.equalsTo(new BasicNameValuePair("Name0", null))); @@ -94,7 +94,7 @@ private static String format(final NameValuePair... nvps) { } @Test - public void testFormat() throws Exception { + 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=")); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/pool/TestLaxConnPool.java b/httpcore5/src/test/java/org/apache/hc/core5/pool/TestLaxConnPool.java index 129f7c6562..29263c6a03 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/pool/TestLaxConnPool.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/pool/TestLaxConnPool.java @@ -27,8 +27,14 @@ 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; @@ -39,10 +45,10 @@ import org.mockito.ArgumentMatchers; import org.mockito.Mockito; -public class TestLaxConnPool { +class TestLaxConnPool { @Test - public void testEmptyPool() throws Exception { + void testEmptyPool() { try (final LaxConnPool pool = new LaxConnPool<>(2)) { final PoolStats totals = pool.getTotalStats(); Assertions.assertEquals(0, totals.getAvailable()); @@ -60,13 +66,13 @@ public void testEmptyPool() throws Exception { } @Test - public void testInvalidConstruction() throws Exception { + void testInvalidConstruction() { Assertions.assertThrows(IllegalArgumentException.class, () -> new LaxConnPool(-1)); } @Test - public void testLeaseRelease() throws Exception { + 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); @@ -101,21 +107,66 @@ public void testLeaseRelease() throws Exception { } @Test - public void testLeaseInvalid() throws Exception { + 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 - public void testReleaseUnknownEntry() throws Exception { + void testReleaseUnknownEntry() { try (final LaxConnPool pool = new LaxConnPool<>(2)) { Assertions.assertThrows(IllegalStateException.class, () -> pool.release(new PoolEntry<>("somehost"), true)); } } @Test - public void testMaxLimits() throws Exception { + 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); @@ -195,7 +246,7 @@ public void testMaxLimits() throws Exception { } @Test - public void testCreateNewIfExpired() throws Exception { + void testCreateNewIfExpired() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); try (final LaxConnPool pool = new LaxConnPool<>(2)) { @@ -229,7 +280,7 @@ public void testCreateNewIfExpired() throws Exception { } @Test - public void testCloseExpired() throws Exception { + void testCloseExpired() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); final HttpConnection conn2 = Mockito.mock(HttpConnection.class); @@ -272,7 +323,7 @@ public void testCloseExpired() throws Exception { } @Test - public void testCloseIdle() throws Exception { + void testCloseIdle() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); final HttpConnection conn2 = Mockito.mock(HttpConnection.class); @@ -330,7 +381,7 @@ public void testCloseIdle() throws Exception { } @Test - public void testLeaseRequestTimeout() throws Exception { + void testLeaseRequestTimeout() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); try (final LaxConnPool pool = new LaxConnPool<>(1)) { @@ -356,7 +407,7 @@ public void testLeaseRequestTimeout() throws Exception { } @Test - public void testLeaseRequestCanceled() throws Exception { + void testLeaseRequestCanceled() throws Exception { try (final LaxConnPool pool = new LaxConnPool<>(1)) { final Future> future1 = pool.lease("somehost", null, @@ -380,14 +431,14 @@ public void testLeaseRequestCanceled() throws Exception { } @Test - public void testGetStatsInvalid() throws Exception { + void testGetStatsInvalid() { try (final LaxConnPool pool = new LaxConnPool<>(2)) { Assertions.assertThrows(NullPointerException.class, () -> pool.getStats(null)); } } @Test - public void testSetMaxInvalid() throws Exception { + void testSetMaxInvalid() { try (final LaxConnPool pool = new LaxConnPool<>(2)) { Assertions.assertThrows(NullPointerException.class, () -> pool.setMaxPerRoute(null, 1)); Assertions.assertThrows(IllegalArgumentException.class, () -> pool.setDefaultMaxPerRoute(-1)); @@ -395,7 +446,7 @@ public void testSetMaxInvalid() throws Exception { } @Test - public void testShutdown() throws Exception { + void testShutdown() { final LaxConnPool pool = new LaxConnPool<>(2); pool.close(CloseMode.GRACEFUL); Assertions.assertThrows(IllegalStateException.class, () -> pool.lease("somehost", null)); @@ -404,7 +455,7 @@ public void testShutdown() throws Exception { } @Test - public void testClose() { + void testClose() { final LaxConnPool pool = new LaxConnPool<>(2); pool.setMaxPerRoute("someRoute", 2); pool.close(); @@ -415,7 +466,7 @@ public void testClose() { } @Test - public void testGetMaxPerRoute() { + void testGetMaxPerRoute() { final String route = "someRoute"; final int max = 2; try (final LaxConnPool pool = new LaxConnPool<>(2)) { diff --git a/httpcore5/src/test/java/org/apache/hc/core5/pool/TestPoolEntry.java b/httpcore5/src/test/java/org/apache/hc/core5/pool/TestPoolEntry.java index 65209acfc3..24e61b6f26 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/pool/TestPoolEntry.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/pool/TestPoolEntry.java @@ -39,19 +39,19 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -public class TestPoolEntry { +class TestPoolEntry { private AtomicLong count; private Supplier currentTimeSupplier; @BeforeEach - public void setup() { + void setup() { count = new AtomicLong(1); currentTimeSupplier = () -> count.addAndGet(1); } @Test - public void testBasics() throws Exception { + void testBasics() { final PoolEntry entry1 = new PoolEntry<>( "route1", TimeValue.of(10L, TimeUnit.MILLISECONDS), currentTimeSupplier); @@ -72,13 +72,13 @@ public void testBasics() throws Exception { } @Test - public void testNullConstructor() throws Exception { + void testNullConstructor() { Assertions.assertThrows(NullPointerException.class, () -> new PoolEntry(null)); } @Test - public void testValidInfinitely() throws Exception { + void testValidInfinitely() { final PoolEntry entry1 = new PoolEntry<>( "route1", TimeValue.ZERO_MILLISECONDS, currentTimeSupplier); entry1.assignConnection(Mockito.mock(HttpConnection.class)); @@ -87,7 +87,7 @@ public void testValidInfinitely() throws Exception { } @Test - public void testExpiry() throws Exception { + void testExpiry() { final PoolEntry entry1 = new PoolEntry<>( "route1", TimeValue.ZERO_MILLISECONDS, currentTimeSupplier); entry1.assignConnection(Mockito.mock(HttpConnection.class)); @@ -109,7 +109,7 @@ public void testExpiry() throws Exception { } @Test - public void testInvalidExpiry() throws Exception { + void testInvalidExpiry() { final PoolEntry entry = new PoolEntry<>( "route1", TimeValue.of(0L, TimeUnit.MILLISECONDS), currentTimeSupplier); Assertions.assertThrows(NullPointerException.class, () -> @@ -117,7 +117,7 @@ public void testInvalidExpiry() throws Exception { } @Test - public void testExpiryDoesNotOverflow() { + void testExpiryDoesNotOverflow() { final PoolEntry entry = new PoolEntry<>( "route1", TimeValue.of(Long.MAX_VALUE, TimeUnit.MILLISECONDS), currentTimeSupplier); entry.assignConnection(Mockito.mock(HttpConnection.class)); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/pool/TestStrictConnPool.java b/httpcore5/src/test/java/org/apache/hc/core5/pool/TestStrictConnPool.java index c00b848fb0..08b7798bdd 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/pool/TestStrictConnPool.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/pool/TestStrictConnPool.java @@ -27,11 +27,16 @@ 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; @@ -43,10 +48,10 @@ import org.mockito.ArgumentMatchers; import org.mockito.Mockito; -public class TestStrictConnPool { +class TestStrictConnPool { @Test - public void testEmptyPool() throws Exception { + void testEmptyPool() { try (final StrictConnPool pool = new StrictConnPool<>(2, 10)) { final PoolStats totals = pool.getTotalStats(); Assertions.assertEquals(0, totals.getAvailable()); @@ -64,7 +69,7 @@ public void testEmptyPool() throws Exception { } @Test - public void testInvalidConstruction() throws Exception { + void testInvalidConstruction() { Assertions.assertThrows(IllegalArgumentException.class, () -> new StrictConnPool(-1, 1)); Assertions.assertThrows(IllegalArgumentException.class, () -> @@ -72,7 +77,7 @@ public void testInvalidConstruction() throws Exception { } @Test - public void testLeaseRelease() throws Exception { + 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); @@ -107,7 +112,52 @@ public void testLeaseRelease() throws Exception { } @Test - public void testLeaseInvalid() throws Exception { + 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)); @@ -117,7 +167,7 @@ public void testLeaseInvalid() throws Exception { } @Test - public void testReleaseUnknownEntry() throws Exception { + void testReleaseUnknownEntry() { try (final StrictConnPool pool = new StrictConnPool<>(2, 2)) { Assertions.assertThrows(IllegalStateException.class, () -> pool.release(new PoolEntry<>("somehost"), true)); @@ -125,7 +175,7 @@ public void testReleaseUnknownEntry() throws Exception { } @Test - public void testMaxLimits() throws Exception { + 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); @@ -206,7 +256,7 @@ public void testMaxLimits() throws Exception { } @Test - public void testConnectionRedistributionOnTotalMaxLimit() throws Exception { + 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); @@ -293,7 +343,7 @@ public void testConnectionRedistributionOnTotalMaxLimit() throws Exception { } @Test - public void testStatefulConnectionRedistributionOnPerRouteMaxLimit() throws Exception { + void testStatefulConnectionRedistributionOnPerRouteMaxLimit() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); final HttpConnection conn2 = Mockito.mock(HttpConnection.class); @@ -357,7 +407,7 @@ public void testStatefulConnectionRedistributionOnPerRouteMaxLimit() throws Exce } @Test - public void testCreateNewIfExpired() throws Exception { + void testCreateNewIfExpired() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); try (final StrictConnPool pool = new StrictConnPool<>(2, 2)) { @@ -391,7 +441,7 @@ public void testCreateNewIfExpired() throws Exception { } @Test - public void testCloseExpired() throws Exception { + void testCloseExpired() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); final HttpConnection conn2 = Mockito.mock(HttpConnection.class); @@ -434,7 +484,7 @@ public void testCloseExpired() throws Exception { } @Test - public void testCloseIdle() throws Exception { + void testCloseIdle() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); final HttpConnection conn2 = Mockito.mock(HttpConnection.class); @@ -490,7 +540,7 @@ public void testCloseIdle() throws Exception { } @Test - public void testLeaseRequestTimeout() throws Exception { + void testLeaseRequestTimeout() throws Exception { final HttpConnection conn1 = Mockito.mock(HttpConnection.class); try (final StrictConnPool pool = new StrictConnPool<>(1, 1)) { @@ -531,7 +581,7 @@ private HoldInternalLockThread(final StrictConnPool pool } @Test - public void testLeaseRequestLockTimeout() throws Exception { + void testLeaseRequestLockTimeout() throws Exception { final StrictConnPool pool = new StrictConnPool<>(1, 1); final CountDownLatch lockHeld = new CountDownLatch(1); final Thread holdInternalLock = new HoldInternalLockThread(pool, lockHeld); @@ -549,7 +599,7 @@ public void testLeaseRequestLockTimeout() throws Exception { } @Test - public void testLeaseRequestInterrupted() throws Exception { + void testLeaseRequestInterrupted() throws Exception { final StrictConnPool pool = new StrictConnPool<>(1, 1); final CountDownLatch lockHeld = new CountDownLatch(1); final Thread holdInternalLock = new HoldInternalLockThread(pool, lockHeld); @@ -567,7 +617,7 @@ public void testLeaseRequestInterrupted() throws Exception { } @Test - public void testLeaseRequestCanceled() throws Exception { + void testLeaseRequestCanceled() throws Exception { try (final StrictConnPool pool = new StrictConnPool<>(1, 1)) { final Future> future1 = pool.lease("somehost", null, @@ -591,14 +641,14 @@ public void testLeaseRequestCanceled() throws Exception { } @Test - public void testGetStatsInvalid() throws Exception { + void testGetStatsInvalid() { try (final StrictConnPool pool = new StrictConnPool<>(2, 2)) { Assertions.assertThrows(NullPointerException.class, () -> pool.getStats(null)); } } @Test - public void testSetMaxInvalid() throws Exception { + void testSetMaxInvalid() { try (final StrictConnPool pool = new StrictConnPool<>(2, 2)) { Assertions.assertThrows(IllegalArgumentException.class, () -> pool.setMaxTotal(-1)); @@ -610,7 +660,7 @@ public void testSetMaxInvalid() throws Exception { } @Test - public void testSetMaxPerRoute() throws Exception { + void testSetMaxPerRoute() { try (final StrictConnPool pool = new StrictConnPool<>(2, 2)) { pool.setMaxPerRoute("somehost", 1); Assertions.assertEquals(1, pool.getMaxPerRoute("somehost")); @@ -622,7 +672,7 @@ public void testSetMaxPerRoute() throws Exception { } @Test - public void testShutdown() throws Exception { + void testShutdown() { final StrictConnPool pool = new StrictConnPool<>(2, 2); pool.close(CloseMode.GRACEFUL); Assertions.assertThrows(IllegalStateException.class, () -> pool.lease("somehost", null)); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/reactor/IOReactorConfigTest.java b/httpcore5/src/test/java/org/apache/hc/core5/reactor/IOReactorConfigTest.java index 9147593458..ff1bb120bf 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/reactor/IOReactorConfigTest.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/reactor/IOReactorConfigTest.java @@ -33,9 +33,9 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class IOReactorConfigTest { +class IOReactorConfigTest { @Test - public void testCustomIOReactorConfig() throws Exception { + void testCustomIOReactorConfig() { final IOReactorConfig reactorConfig = IOReactorConfig.custom() .setSelectInterval(TimeValue.ofMilliseconds(500)) .setIoThreadCount(2) @@ -48,6 +48,9 @@ public void testCustomIOReactorConfig() throws Exception { .setSndBufSize(32767) .setRcvBufSize(8192) .setBacklogSize(5) + .setTcpKeepIdle(100) + .setTcpKeepInterval(12) + .setTcpKeepCount(4) .setSocksProxyAddress(new InetSocketAddress(8888)) .setSocksProxyUsername("socksProxyUsername") .setSocksProxyPassword("socksProxyPassword") @@ -64,6 +67,9 @@ public void testCustomIOReactorConfig() throws Exception { 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()); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/reactor/IOWorkersTest.java b/httpcore5/src/test/java/org/apache/hc/core5/reactor/IOWorkersTest.java index f70453699d..1de15a4b02 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/reactor/IOWorkersTest.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/reactor/IOWorkersTest.java @@ -30,10 +30,10 @@ import org.junit.jupiter.api.Test; -public class IOWorkersTest { +class IOWorkersTest { @Test - public void testIndexOverflow() { + void testIndexOverflow() { final SingleCoreIOReactor reactor = new SingleCoreIOReactor(null, mock(IOEventHandlerFactory.class), IOReactorConfig.DEFAULT, null, null, null); final IOWorkers.Selector selector = IOWorkers.newSelector(new SingleCoreIOReactor[]{reactor, reactor, reactor}); for (long i = Integer.MAX_VALUE - 10; i < (long) Integer.MAX_VALUE + 10; i++) { diff --git a/httpcore5/src/test/java/org/apache/hc/core5/reactor/TestAbstractIOSessionPool.java b/httpcore5/src/test/java/org/apache/hc/core5/reactor/TestAbstractIOSessionPool.java index 2a6ccc8109..581b79c308 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/reactor/TestAbstractIOSessionPool.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/reactor/TestAbstractIOSessionPool.java @@ -48,7 +48,7 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -public class TestAbstractIOSessionPool { +class TestAbstractIOSessionPool { @Mock private Future connectFuture; @@ -66,7 +66,7 @@ public class TestAbstractIOSessionPool { private AbstractIOSessionPool impl; @BeforeEach - public void setup() { + void setup() { MockitoAnnotations.openMocks(this); impl = Mockito.mock(AbstractIOSessionPool.class, Mockito.withSettings() .defaultAnswer(Answers.CALLS_REAL_METHODS) @@ -74,7 +74,7 @@ public void setup() { } @Test - public void testGetSessions() throws Exception { + void testGetSessions() throws Exception { Mockito.when(impl.connectSession( ArgumentMatchers.anyString(), @@ -134,7 +134,7 @@ public void testGetSessions() throws Exception { } @Test - public void testGetSessionConnectFailure() throws Exception { + void testGetSessionConnectFailure() { Mockito.when(impl.connectSession( ArgumentMatchers.anyString(), @@ -166,7 +166,7 @@ public void testGetSessionConnectFailure() throws Exception { } @Test - public void testShutdownPool() throws Exception { + void testShutdownPool() { final AbstractIOSessionPool.PoolEntry entry1 = impl.getPoolEntry("host1"); assertThat(entry1, CoreMatchers.notNullValue()); entry1.session = ioSession1; @@ -191,7 +191,7 @@ public void testShutdownPool() throws Exception { } @Test - public void testCloseIdleSessions() throws Exception { + void testCloseIdleSessions() { final AbstractIOSessionPool.PoolEntry entry1 = impl.getPoolEntry("host1"); assertThat(entry1, CoreMatchers.notNullValue()); entry1.session = ioSession1; @@ -210,7 +210,7 @@ public void testCloseIdleSessions() throws Exception { } @Test - public void testEnumSessions() throws Exception { + void testEnumSessions() { final AbstractIOSessionPool.PoolEntry entry1 = impl.getPoolEntry("host1"); assertThat(entry1, CoreMatchers.notNullValue()); entry1.session = ioSession1; @@ -225,7 +225,7 @@ public void testEnumSessions() throws Exception { } @Test - public void testGetSessionReconnectAfterValidate() throws Exception { + void testGetSessionReconnectAfterValidate() { final AbstractIOSessionPool.PoolEntry entry1 = impl.getPoolEntry("somehost"); assertThat(entry1, CoreMatchers.notNullValue()); entry1.session = ioSession1; @@ -246,7 +246,7 @@ public void testGetSessionReconnectAfterValidate() throws Exception { } @Test - public void testGetSessionReconnectIfClosed() throws Exception { + void testGetSessionReconnectIfClosed() { final AbstractIOSessionPool.PoolEntry entry1 = impl.getPoolEntry("somehost"); assertThat(entry1, CoreMatchers.notNullValue()); entry1.session = ioSession1; @@ -262,7 +262,7 @@ public void testGetSessionReconnectIfClosed() throws Exception { } @Test - public void testGetSessionConnectUnknownHost() throws Exception { + void testGetSessionConnectUnknownHost() { Mockito.when(connectFuture.isDone()).thenReturn(true); Mockito.when(impl.connectSession( diff --git a/httpcore5/src/test/java/org/apache/hc/core5/reactor/ssl/SSLIOSessionTest.java b/httpcore5/src/test/java/org/apache/hc/core5/reactor/ssl/SSLIOSessionTest.java index 290434fbf8..ace6dc8d87 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/reactor/ssl/SSLIOSessionTest.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/reactor/ssl/SSLIOSessionTest.java @@ -71,7 +71,7 @@ class SSLIOSessionTest { private SSLSession sslSession; @BeforeEach - public void setUp() throws SSLException { + void setUp() throws SSLException { final String protocol = "TestProtocol"; // Arrange diff --git a/httpcore5/src/test/java/org/apache/hc/core5/ssl/DummyProvider.java b/httpcore5/src/test/java/org/apache/hc/core5/ssl/DummyProvider.java index 6d82a48ab8..a19625fcdd 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/ssl/DummyProvider.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/ssl/DummyProvider.java @@ -31,6 +31,7 @@ import java.security.Security; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; public class DummyProvider extends Provider { @@ -40,8 +41,11 @@ public class DummyProvider extends Provider { 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) { @@ -58,7 +62,12 @@ public Service getService(final String type, final String algorithm) { } @Override - public synchronized Set getServices() { - return realJSSEProvider.getServices(); + public Set getServices() { + lock.lock(); + try { + return realJSSEProvider.getServices(); + } finally { + lock.unlock(); + } } } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/ssl/SSLContextsTest.java b/httpcore5/src/test/java/org/apache/hc/core5/ssl/SSLContextsTest.java index 49e086775c..6862ea97d2 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/ssl/SSLContextsTest.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/ssl/SSLContextsTest.java @@ -43,7 +43,7 @@ import org.junit.jupiter.api.Test; -public class SSLContextsTest { +class SSLContextsTest { @Test void createDefault() { diff --git a/httpcore5/src/test/java/org/apache/hc/core5/ssl/TestSSLContextBuilder.java b/httpcore5/src/test/java/org/apache/hc/core5/ssl/TestSSLContextBuilder.java index b6b46fe630..b4f7a469be 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/ssl/TestSSLContextBuilder.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/ssl/TestSSLContextBuilder.java @@ -54,6 +54,7 @@ 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; @@ -68,7 +69,7 @@ /** * Unit tests for {@link SSLContextBuilder}. */ -public class TestSSLContextBuilder { +class TestSSLContextBuilder { static final String PROVIDER_SUN_JSSE = "SunJSSE"; static final String PROVIDER_SUN_JCE = "SunJCE"; @@ -81,7 +82,7 @@ private static boolean isWindows() { private ExecutorService executorService; @AfterEach - public void cleanup() throws Exception { + void cleanup() throws Exception { if (this.executorService != null) { this.executorService.shutdown(); this.executorService.awaitTermination(5, TimeUnit.SECONDS); @@ -93,7 +94,7 @@ private URL getResource(final String name) { } @Test - public void testBuildAllDefaults() throws Exception { + void testBuildAllDefaults() throws Exception { final SSLContext sslContext = SSLContextBuilder.create() .setKeyStoreType(KeyStore.getDefaultType()) .setKeyManagerFactoryAlgorithm(KeyManagerFactory.getDefaultAlgorithm()) @@ -110,7 +111,7 @@ public void testBuildAllDefaults() throws Exception { } @Test - public void testBuildAllNull() throws Exception { + void testBuildAllNull() throws Exception { final SSLContext sslContext = SSLContextBuilder.create() .setKeyStoreType(null) .setKeyManagerFactoryAlgorithm(null) @@ -127,7 +128,7 @@ public void testBuildAllNull() throws Exception { } @Test - public void testBuildAllNull_deprecated() throws Exception { + void testBuildAllNull_deprecated() throws Exception { final SSLContext sslContext = SSLContextBuilder.create() .setProtocol(null) .setSecureRandom(null) @@ -139,12 +140,12 @@ public void testBuildAllNull_deprecated() throws Exception { } @Test - public void testBuildDefault() throws Exception { - new SSLContextBuilder().build(); + void testBuildDefault() { + Assertions.assertDoesNotThrow(() -> new SSLContextBuilder().build()); } @Test - public void testBuildNoSuchKeyManagerFactoryAlgorithm() throws Exception { + void testBuildNoSuchKeyManagerFactoryAlgorithm() { final URL resource1 = getResource("/test-keypasswd.p12"); final String storePassword = "nopassword"; final String keyPassword = "password"; @@ -156,7 +157,7 @@ public void testBuildNoSuchKeyManagerFactoryAlgorithm() throws Exception { } @Test - public void testBuildNoSuchKeyStoreType() throws Exception { + void testBuildNoSuchKeyStoreType() { final URL resource1 = getResource("/test-keypasswd.p12"); final String storePassword = "nopassword"; final String keyPassword = "password"; @@ -168,7 +169,7 @@ public void testBuildNoSuchKeyStoreType() throws Exception { } @Test - public void testBuildNoSuchTrustManagerFactoryAlgorithm() throws Exception { + void testBuildNoSuchTrustManagerFactoryAlgorithm() { final URL resource1 = getResource("/test-keypasswd.p12"); final String storePassword = "nopassword"; Assertions.assertThrows(NoSuchAlgorithmException.class, () -> @@ -179,7 +180,7 @@ public void testBuildNoSuchTrustManagerFactoryAlgorithm() throws Exception { } @Test - public void testBuildWithProvider() throws Exception { + void testBuildWithProvider() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; @@ -192,7 +193,7 @@ public void testBuildWithProvider() throws Exception { } @Test - public void testBuildWithProviderName() throws Exception { + void testBuildWithProviderName() throws Exception { final DummyProvider provider = new DummyProvider(); Security.insertProviderAt(provider, 1); @@ -213,7 +214,7 @@ public void testBuildWithProviderName() throws Exception { } @Test - public void testBuildKSWithNoSuchProvider() { + void testBuildKSWithNoSuchProvider() { Assertions.assertThrows(NoSuchProviderException.class, () -> SSLContextBuilder.create() .setKeyStoreProvider("no-such-provider") @@ -221,7 +222,7 @@ public void testBuildKSWithNoSuchProvider() { } @Test - public void testBuildKSWithProvider() throws Exception { + void testBuildKSWithProvider() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; @@ -234,7 +235,7 @@ public void testBuildKSWithProvider() throws Exception { } @Test - public void testBuildKSWithProviderName() throws Exception { + void testBuildKSWithProviderName() throws Exception { final DummyProvider provider = new DummyProvider(); Security.insertProviderAt(provider, 1); @@ -255,7 +256,7 @@ public void testBuildKSWithProviderName() throws Exception { } @Test - public void testBuildTSWithNoSuchProvider() { + void testBuildTSWithNoSuchProvider() { Assertions.assertThrows(NoSuchProviderException.class, ()-> SSLContextBuilder.create() .setTrustStoreProvider("no-such-provider") @@ -263,7 +264,7 @@ public void testBuildTSWithNoSuchProvider() { } @Test - public void testBuildTSWithProvider() throws Exception { + void testBuildTSWithProvider() throws Exception { final DummyProvider provider = new DummyProvider(); SSLContextBuilder.create() .setTrustStoreProvider(provider) @@ -273,7 +274,7 @@ public void testBuildTSWithProvider() throws Exception { } @Test - public void testBuildTSWithProviderName() throws Exception { + void testBuildTSWithProviderName() throws Exception { final DummyProvider provider = new DummyProvider(); Security.insertProviderAt(provider, 1); @@ -292,7 +293,7 @@ public void testBuildTSWithProviderName() throws Exception { @Test - public void testKeyWithAlternatePasswordInvalid() throws Exception { + void testKeyWithAlternatePasswordInvalid() { final URL resource1 = getResource("/test-keypasswd.p12"); final String storePassword = "nopassword"; final String keyPassword = "!password"; @@ -304,7 +305,7 @@ public void testKeyWithAlternatePasswordInvalid() throws Exception { } @Test - public void testSSLHandshakeServerTrusted() throws Exception { + void testSSLHandshakeServerTrusted() throws Exception { final URL resource1 = getResource("/test.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; @@ -344,7 +345,7 @@ public void testSSLHandshakeServerTrusted() throws Exception { } @Test - public void testSSLHandshakeServerNotTrusted() throws Exception { + void testSSLHandshakeServerNotTrusted() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; @@ -376,7 +377,7 @@ public void testSSLHandshakeServerNotTrusted() throws Exception { } @Test - public void testSSLHandshakeServerCustomTrustStrategy() throws Exception { + void testSSLHandshakeServerCustomTrustStrategy() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; @@ -443,7 +444,7 @@ public void testSSLHandshakeServerCustomTrustStrategy() throws Exception { } @Test - public void testSSLHandshakeClientUnauthenticated() throws Exception { + void testSSLHandshakeClientUnauthenticated() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; @@ -462,9 +463,8 @@ public void testSSLHandshakeClientUnauthenticated() throws Exception { this.executorService = Executors.newSingleThreadExecutor(); final Future future = this.executorService.submit(() -> { - final SSLSocket socket = (SSLSocket) serverSocket.accept(); Principal clientPrincipal = null; - try { + try (SSLSocket socket = (SSLSocket) serverSocket.accept()) { final SSLSession session = socket.getSession(); try { clientPrincipal = session.getPeerPrincipal(); @@ -473,8 +473,6 @@ public void testSSLHandshakeClientUnauthenticated() throws Exception { final OutputStream outputStream = socket.getOutputStream(); outputStream.write(new byte [] {'H', 'i'}); outputStream.flush(); - } finally { - socket.close(); } return clientPrincipal; }); @@ -495,7 +493,7 @@ public void testSSLHandshakeClientUnauthenticated() throws Exception { } @Test - public void testSSLHandshakeClientUnauthenticatedError() throws Exception { + void testSSLHandshakeClientUnauthenticatedError() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; @@ -533,7 +531,7 @@ public void testSSLHandshakeClientUnauthenticatedError() throws Exception { } @Test - public void testSSLHandshakeClientAuthenticated() throws Exception { + void testSSLHandshakeClientAuthenticated() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; @@ -579,7 +577,7 @@ public void testSSLHandshakeClientAuthenticated() throws Exception { } @Test - public void testSSLHandshakeClientAuthenticatedPrivateKeyStrategy() throws Exception { + void testSSLHandshakeClientAuthenticatedPrivateKeyStrategy() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; @@ -630,7 +628,7 @@ public void testSSLHandshakeClientAuthenticatedPrivateKeyStrategy() throws Excep @Test - public void testSSLHandshakeProtocolMismatch1() throws Exception { + void testSSLHandshakeProtocolMismatch1() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; @@ -673,7 +671,7 @@ public void testSSLHandshakeProtocolMismatch1() throws Exception { } @Test - public void testSSLHandshakeProtocolMismatch2() throws Exception { + void testSSLHandshakeProtocolMismatch2() throws Exception { final URL resource1 = getResource("/test-server.p12"); final String storePassword = "nopassword"; final String keyPassword = "nopassword"; @@ -716,4 +714,68 @@ public void testSSLHandshakeProtocolMismatch2() throws Exception { } } + @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(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(); + } + } + }); + } + } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/util/TestArgs.java b/httpcore5/src/test/java/org/apache/hc/core5/util/TestArgs.java index af4be3bf0b..7d645c8f9b 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/util/TestArgs.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/util/TestArgs.java @@ -42,226 +42,226 @@ /** * Unit tests for {@link Args}. */ -public class TestArgs { +class TestArgs { @Test - public void testArgCheckPass() { + void testArgCheckPass() { Args.check(true, "All is well"); } @Test - public void testArgCheckFail() { + void testArgCheckFail() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.check(false, "Oopsie")); } @Test - public void testArgNotNullPass() { + void testArgNotNullPass() { final String stuff = "stuff"; Assertions.assertSame(stuff, Args.notNull(stuff, "Stuff")); } @Test - public void testArgNotNullFail() { + void testArgNotNullFail() { Assertions.assertThrows(NullPointerException.class, () -> Args.notNull(null, "Stuff")); } @Test - public void testArgNotEmptyPass() { + void testArgNotEmptyPass() { final String stuff = "stuff"; Assertions.assertSame(stuff, Args.notEmpty(stuff, "Stuff")); } @Test - public void testArgNotEmptyFail1() { + void testArgNotEmptyFail1() { Assertions.assertThrows(NullPointerException.class, () -> Args.notEmpty((String) null, "Stuff")); } @Test - public void testArgNotEmptyFail2() { + void testArgNotEmptyFail2() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.notEmpty("", "Stuff")); } @Test - public void testArgNotBlankFail1() { + void testArgNotBlankFail1() { Assertions.assertThrows(NullPointerException.class, () -> Args.notBlank((String) null, "Stuff")); } @Test - public void testArgNotBlankFail2() { + void testArgNotBlankFail2() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.notBlank("", "Stuff")); } @Test - public void testArgNotBlankFail3() { + void testArgNotBlankFail3() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.notBlank(" \t \n\r", "Stuff")); } @Test - public void testArgCollectionNotEmptyPass() { + void testArgCollectionNotEmptyPass() { final List list = Collections.singletonList("stuff"); Assertions.assertSame(list, Args.notEmpty(list, "List")); } @Test - public void testArgCollectionNotEmptyFail1() { + void testArgCollectionNotEmptyFail1() { Assertions.assertThrows(NullPointerException.class, () -> Args.notEmpty((List) null, "List")); } @Test - public void testArgCollectionNotEmptyFail2() { + void testArgCollectionNotEmptyFail2() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.notEmpty(Collections.emptyList(), "List")); } @Test - public void testPositiveIntPass() { + void testPositiveIntPass() { Assertions.assertEquals(1, Args.positive(1, "Number")); } @Test - public void testPositiveIntFail1() { + void testPositiveIntFail1() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.positive(-1, "Number")); } @Test - public void testPositiveIntFail2() { + void testPositiveIntFail2() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.positive(0, "Number")); } @Test - public void testPositiveLongPass() { + void testPositiveLongPass() { Assertions.assertEquals(1L, Args.positive(1L, "Number")); } @Test - public void testPositiveTimeValuePass() throws ParseException { + void testPositiveTimeValuePass() throws ParseException { final Timeout timeout = Timeout.parse("1200 MILLISECONDS"); Assertions.assertEquals(timeout, Args.positive(timeout, "No Error")); } @Test - public void testPositiveLongFail1() { + void testPositiveLongFail1() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.positive(-1L, "Number")); } @Test - public void testPositiveLongFail2() { + void testPositiveLongFail2() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.positive(0L, "Number")); } @Test - public void testNotNegativeIntPass1() { + void testNotNegativeIntPass1() { Assertions.assertEquals(1, Args.notNegative(1, "Number")); } @Test - public void testNotNegativeIntPass2() { + void testNotNegativeIntPass2() { Assertions.assertEquals(0, Args.notNegative(0, "Number")); } @Test - public void testNotNegativeIntFail1() { + void testNotNegativeIntFail1() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.notNegative(-1, "Number")); } @Test - public void testNotNegativeLongPass1() { + void testNotNegativeLongPass1() { Assertions.assertEquals(1L, Args.notNegative(1L, "Number")); } @Test - public void testNotNegativeLongPass2() { + void testNotNegativeLongPass2() { Assertions.assertEquals(0L, Args.notNegative(0L, "Number")); } @Test - public void testNotNegativeLongFail1() { + void testNotNegativeLongFail1() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.notNegative(-1L, "Number")); } @Test - public void testIntSmallestRangeOK() { + void testIntSmallestRangeOK() { Args.checkRange(0, 0, 0, "Number"); } @Test - public void testIntSmallestRangeFailLow() { + void testIntSmallestRangeFailLow() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.checkRange(-1, 0, 0, "Number")); } @Test - public void testIntRangeFailLow() { + void testIntRangeFailLow() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.checkRange(-101, -100, 100, "Number")); } @Test - public void testIntRangeFailHigh() { + void testIntRangeFailHigh() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.checkRange(101, -100, 100, "Number")); } @Test - public void testIntSmallestRangeFailHigh() { + void testIntSmallestRangeFailHigh() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.checkRange(1, 0, 0, "Number")); } @Test - public void testIntFullRangeOK() { + void testIntFullRangeOK() { Args.checkRange(0, Integer.MIN_VALUE, Integer.MAX_VALUE, "Number"); } @Test - public void testLongSmallestRangeOK() { + void testLongSmallestRangeOK() { Args.checkRange(0L, 0L, 0L, "Number"); } @Test - public void testLongSmallestRangeFailLow() { + void testLongSmallestRangeFailLow() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.checkRange(-1L, 0L, 0L, "Number")); } @Test - public void testLongRangeFailLow() { + void testLongRangeFailLow() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.checkRange(-101L, -100L, 100L, "Number")); } @Test - public void testLongRangeFailHigh() { + void testLongRangeFailHigh() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.checkRange(101L, -100L, 100L, "Number")); } @Test - public void testLongSmallestRangeFailHigh() { + void testLongSmallestRangeFailHigh() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.checkRange(1L, 0L, 0L, "Number")); } @Test - public void testLongFullRangeOK() { + void testLongFullRangeOK() { Args.checkRange(0L, Long.MIN_VALUE, Long.MAX_VALUE, "Number"); } @Test - public void testIsEmpty() { + void testIsEmpty() { final String[] NON_EMPTY_ARRAY = new String[] { "ABG", "NML", }; @@ -288,13 +288,13 @@ public void testIsEmpty() { } @Test - public void testcontainsNoBlanks() { + void testContainsNoBlanks() { final String stuff = "abg"; Assertions.assertSame(stuff, Args.containsNoBlanks(stuff, "abg")); } @Test - public void check() { + void check() { Assertions.assertThrows(IllegalArgumentException.class, () -> Args.check(false, "Error,", "ABG")); } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/util/TestAsserts.java b/httpcore5/src/test/java/org/apache/hc/core5/util/TestAsserts.java index 698f9f37ad..434d353270 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/util/TestAsserts.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/util/TestAsserts.java @@ -33,51 +33,51 @@ /** * Unit tests for {@link Asserts}. */ -public class TestAsserts { +class TestAsserts { @Test - public void testExpressionCheckPass() { + void testExpressionCheckPass() { Asserts.check(true, "All is well"); } @Test - public void testExpressionCheckFail() { + void testExpressionCheckFail() { Assertions.assertThrows(IllegalStateException.class, () -> Asserts.check(false, "Oopsie")); } @Test - public void testExpressionNotNullFail() { + void testExpressionNotNullFail() { Assertions.assertThrows(IllegalStateException.class, () -> Asserts.notNull(null, "Stuff")); } @Test - public void testExpressionNotEmptyFail1() { + void testExpressionNotEmptyFail1() { Assertions.assertThrows(IllegalStateException.class, () -> Asserts.notEmpty(null, "Stuff")); } @Test - public void testExpressionNotEmptyFail2() { + void testExpressionNotEmptyFail2() { Assertions.assertThrows(IllegalStateException.class, () -> Asserts.notEmpty("", "Stuff")); } @Test - public void testExpressionNotEmptyBlank1() { + void testExpressionNotEmptyBlank1() { Assertions.assertThrows(IllegalStateException.class, () -> Asserts.notBlank(null, "Stuff")); } @Test - public void testExpressionNotEmptyBlank2() { + void testExpressionNotEmptyBlank2() { Assertions.assertThrows(IllegalStateException.class, () -> Asserts.notBlank("", "Stuff")); } @Test - public void testExpressionNotBlankFail3() { + void testExpressionNotBlankFail3() { Assertions.assertThrows(IllegalStateException.class, () -> Asserts.notBlank(" \t \n\r", "Stuff")); } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/util/TestByteArrayBuffer.java b/httpcore5/src/test/java/org/apache/hc/core5/util/TestByteArrayBuffer.java index 2d9a0e7ba6..c2444b049b 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/util/TestByteArrayBuffer.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/util/TestByteArrayBuffer.java @@ -41,10 +41,10 @@ * Unit tests for {@link ByteArrayBuffer}. * */ -public class TestByteArrayBuffer { +class TestByteArrayBuffer { @Test - public void testConstructor() throws Exception { + void testConstructor() { final ByteArrayBuffer buffer = new ByteArrayBuffer(16); Assertions.assertEquals(16, buffer.capacity()); Assertions.assertEquals(0, buffer.length()); @@ -54,7 +54,7 @@ public void testConstructor() throws Exception { } @Test - public void testSimpleAppend() throws Exception { + void testSimpleAppend() { final ByteArrayBuffer buffer = new ByteArrayBuffer(16); Assertions.assertEquals(16, buffer.capacity()); Assertions.assertEquals(0, buffer.length()); @@ -86,7 +86,7 @@ public void testSimpleAppend() throws Exception { } @Test - public void testExpandAppend() throws Exception { + void testExpandAppend() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); Assertions.assertEquals(4, buffer.capacity()); @@ -105,7 +105,7 @@ public void testExpandAppend() throws Exception { } @Test - public void testAppendHeapByteBuffer() { + void testAppendHeapByteBuffer() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); Assertions.assertEquals(4, buffer.capacity()); @@ -126,7 +126,7 @@ public void testAppendHeapByteBuffer() { } @Test - public void testAppendHeapByteBufferWithOffset() { + void testAppendHeapByteBufferWithOffset() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); Assertions.assertEquals(4, buffer.capacity()); @@ -150,7 +150,7 @@ public void testAppendHeapByteBufferWithOffset() { } @Test - public void testAppendDirectByteBuffer() { + void testAppendDirectByteBuffer() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); Assertions.assertEquals(4, buffer.capacity()); @@ -172,7 +172,7 @@ public void testAppendDirectByteBuffer() { } @Test - public void testInvalidAppend() throws Exception { + void testInvalidAppend() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); buffer.append((byte[])null, 0, 0); @@ -185,7 +185,7 @@ public void testInvalidAppend() throws Exception { } @Test - public void testAppendOneByte() throws Exception { + void testAppendOneByte() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); Assertions.assertEquals(4, buffer.capacity()); @@ -202,21 +202,21 @@ public void testAppendOneByte() throws Exception { } @Test - public void testSetLength() throws Exception { + void testSetLength() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); buffer.setLength(2); Assertions.assertEquals(2, buffer.length()); } @Test - public void testSetInvalidLength() throws Exception { + void testSetInvalidLength() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.setLength(-2)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.setLength(200)); } @Test - public void testEnsureCapacity() throws Exception { + void testEnsureCapacity() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); buffer.ensureCapacity(2); Assertions.assertEquals(4, buffer.capacity()); @@ -225,7 +225,7 @@ public void testEnsureCapacity() throws Exception { } @Test - public void testIndexOf() throws Exception { + void testIndexOf() { final byte COLON = (byte) ':'; final byte COMMA = (byte) ','; final byte[] bytes = "name1: value1; name2: value2".getBytes(StandardCharsets.US_ASCII); @@ -244,7 +244,7 @@ public void testIndexOf() throws Exception { } @Test - public void testAppendCharArrayAsAscii() throws Exception { + void testAppendCharArrayAsAscii() { final String s1 = "stuff"; final String s2 = " and more stuff"; final char[] b1 = s1.toCharArray(); @@ -258,28 +258,28 @@ public void testAppendCharArrayAsAscii() throws Exception { } @Test - public void testAppendNullCharArray() throws Exception { + void testAppendNullCharArray() { final ByteArrayBuffer buffer = new ByteArrayBuffer(8); buffer.append((char[])null, 0, 0); Assertions.assertEquals(0, buffer.length()); } @Test - public void testAppendEmptyCharArray() throws Exception { + void testAppendEmptyCharArray() { final ByteArrayBuffer buffer = new ByteArrayBuffer(8); buffer.append(new char[] {}, 0, 0); Assertions.assertEquals(0, buffer.length()); } @Test - public void testAppendNullCharArrayBuffer() throws Exception { + void testAppendNullCharArrayBuffer() { final ByteArrayBuffer buffer = new ByteArrayBuffer(8); buffer.append((CharArrayBuffer)null, 0, 0); Assertions.assertEquals(0, buffer.length()); } @Test - public void testAppendNullByteBuffer() throws Exception { + void testAppendNullByteBuffer() { final ByteArrayBuffer buffer = new ByteArrayBuffer(8); final ByteBuffer nullBuffer = null; buffer.append(nullBuffer); @@ -287,7 +287,7 @@ public void testAppendNullByteBuffer() throws Exception { } @Test - public void testInvalidAppendCharArrayAsAscii() throws Exception { + void testInvalidAppendCharArrayAsAscii() { final ByteArrayBuffer buffer = new ByteArrayBuffer(4); buffer.append((char[])null, 0, 0); @@ -300,7 +300,7 @@ public void testInvalidAppendCharArrayAsAscii() throws Exception { } @Test - public void testSerialization() throws Exception { + void testSerialization() throws Exception { final ByteArrayBuffer orig = new ByteArrayBuffer(32); orig.append(1); orig.append(2); @@ -324,7 +324,7 @@ public void testSerialization() throws Exception { } @Test - public void testControlCharFiltering() throws Exception { + void testControlCharFiltering() { final char[] chars = new char[256]; for (char i = 0; i < 256; i++) { chars[i] = i; @@ -346,7 +346,7 @@ public void testControlCharFiltering() throws Exception { } @Test - public void testUnicodeFiltering() throws Exception { + void testUnicodeFiltering() { // Various languages Assertions.assertEquals("?????", new String(asByteArray("буквы".toCharArray()), StandardCharsets.ISO_8859_1)); Assertions.assertEquals("????", new String(asByteArray("四字熟語".toCharArray()), StandardCharsets.ISO_8859_1)); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/util/TestCharArrayBuffer.java b/httpcore5/src/test/java/org/apache/hc/core5/util/TestCharArrayBuffer.java index 1aa9f8dd9d..50a9cb5936 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/util/TestCharArrayBuffer.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/util/TestCharArrayBuffer.java @@ -40,10 +40,10 @@ * Unit tests for {@link CharArrayBuffer}. * */ -public class TestCharArrayBuffer { +class TestCharArrayBuffer { @Test - public void testConstructor() throws Exception { + void testConstructor() { final CharArrayBuffer buffer = new CharArrayBuffer(16); Assertions.assertEquals(16, buffer.capacity()); Assertions.assertEquals(0, buffer.length()); @@ -53,7 +53,7 @@ public void testConstructor() throws Exception { } @Test - public void testSimpleAppend() throws Exception { + void testSimpleAppend() { final CharArrayBuffer buffer = new CharArrayBuffer(16); Assertions.assertEquals(16, buffer.capacity()); Assertions.assertEquals(0, buffer.length()); @@ -87,7 +87,7 @@ public void testSimpleAppend() throws Exception { } @Test - public void testExpandAppend() throws Exception { + void testExpandAppend() { final CharArrayBuffer buffer = new CharArrayBuffer(4); Assertions.assertEquals(4, buffer.capacity()); @@ -108,7 +108,7 @@ public void testExpandAppend() throws Exception { } @Test - public void testAppendString() throws Exception { + void testAppendString() { final CharArrayBuffer buffer = new CharArrayBuffer(8); buffer.append("stuff"); buffer.append(" and more stuff"); @@ -116,14 +116,14 @@ public void testAppendString() throws Exception { } @Test - public void testAppendNullString() throws Exception { + void testAppendNullString() { final CharArrayBuffer buffer = new CharArrayBuffer(8); buffer.append((String)null); Assertions.assertEquals("null", buffer.toString()); } @Test - public void testAppendCharArrayBuffer() throws Exception { + void testAppendCharArrayBuffer() { final CharArrayBuffer buffer1 = new CharArrayBuffer(8); buffer1.append(" and more stuff"); final CharArrayBuffer buffer2 = new CharArrayBuffer(8); @@ -133,7 +133,7 @@ public void testAppendCharArrayBuffer() throws Exception { } @Test - public void testAppendNullCharArrayBuffer() throws Exception { + void testAppendNullCharArrayBuffer() { final CharArrayBuffer buffer = new CharArrayBuffer(8); buffer.append((CharArrayBuffer)null); buffer.append((CharArrayBuffer)null, 0, 0); @@ -141,7 +141,7 @@ public void testAppendNullCharArrayBuffer() throws Exception { } @Test - public void testAppendSingleChar() throws Exception { + void testAppendSingleChar() { final CharArrayBuffer buffer = new CharArrayBuffer(4); buffer.append('1'); buffer.append('2'); @@ -153,7 +153,7 @@ public void testAppendSingleChar() throws Exception { } @Test - public void testInvalidCharArrayAppend() throws Exception { + void testInvalidCharArrayAppend() { final CharArrayBuffer buffer = new CharArrayBuffer(4); buffer.append((char[])null, 0, 0); @@ -166,21 +166,21 @@ public void testInvalidCharArrayAppend() throws Exception { } @Test - public void testSetLength() throws Exception { + void testSetLength() { final CharArrayBuffer buffer = new CharArrayBuffer(4); buffer.setLength(2); Assertions.assertEquals(2, buffer.length()); } @Test - public void testSetInvalidLength() throws Exception { + void testSetInvalidLength() { final CharArrayBuffer buffer = new CharArrayBuffer(4); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.setLength(-2)); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.setLength(200)); } @Test - public void testEnsureCapacity() throws Exception { + void testEnsureCapacity() { final CharArrayBuffer buffer = new CharArrayBuffer(4); buffer.ensureCapacity(2); Assertions.assertEquals(4, buffer.capacity()); @@ -189,7 +189,7 @@ public void testEnsureCapacity() throws Exception { } @Test - public void testIndexOf() { + void testIndexOf() { final CharArrayBuffer buffer = new CharArrayBuffer(16); buffer.append("name: value"); Assertions.assertEquals(4, buffer.indexOf(':')); @@ -200,7 +200,7 @@ public void testIndexOf() { } @Test - public void testSubstring() { + void testSubstring() { final CharArrayBuffer buffer = new CharArrayBuffer(16); buffer.append(" name: value "); Assertions.assertEquals(5, buffer.indexOf(':')); @@ -212,7 +212,7 @@ public void testSubstring() { } @Test - public void testSubstringIndexOfOutBound() { + void testSubstringIndexOfOutBound() { final CharArrayBuffer buffer = new CharArrayBuffer(16); buffer.append("stuff"); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.substring(-2, 10)); @@ -224,7 +224,7 @@ public void testSubstringIndexOfOutBound() { } @Test - public void testAppendAsciiByteArray() throws Exception { + void testAppendAsciiByteArray() { final String s1 = "stuff"; final String s2 = " and more stuff"; final byte[] b1 = s1.getBytes(StandardCharsets.US_ASCII); @@ -238,7 +238,7 @@ public void testAppendAsciiByteArray() throws Exception { } @Test - public void testAppendISOByteArray() throws Exception { + void testAppendISOByteArray() { final byte[] b = new byte[] {0x00, 0x20, 0x7F, -0x80, -0x01}; final CharArrayBuffer buffer = new CharArrayBuffer(8); @@ -254,21 +254,21 @@ public void testAppendISOByteArray() throws Exception { } @Test - public void testAppendNullByteArray() throws Exception { + void testAppendNullByteArray() { final CharArrayBuffer buffer = new CharArrayBuffer(8); buffer.append((byte[])null, 0, 0); Assertions.assertEquals("", buffer.toString()); } @Test - public void testAppendNullByteArrayBuffer() throws Exception { + void testAppendNullByteArrayBuffer() { final CharArrayBuffer buffer = new CharArrayBuffer(8); buffer.append((ByteArrayBuffer)null, 0, 0); Assertions.assertEquals("", buffer.toString()); } @Test - public void testInvalidAppendAsciiByteArray() throws Exception { + void testInvalidAppendAsciiByteArray() { final CharArrayBuffer buffer = new CharArrayBuffer(4); buffer.append((byte[])null, 0, 0); @@ -281,7 +281,7 @@ public void testInvalidAppendAsciiByteArray() throws Exception { } @Test - public void testSerialization() throws Exception { + void testSerialization() throws Exception { final CharArrayBuffer orig = new CharArrayBuffer(32); orig.append('a'); orig.append('b'); @@ -305,7 +305,7 @@ public void testSerialization() throws Exception { } @Test - public void testSubSequence() { + void testSubSequence() { final CharArrayBuffer buffer = new CharArrayBuffer(16); buffer.append(" name: value "); Assertions.assertEquals(5, buffer.indexOf(':')); @@ -315,7 +315,7 @@ public void testSubSequence() { } @Test - public void testSubSequenceIndexOfOutBound() { + void testSubSequenceIndexOfOutBound() { final CharArrayBuffer buffer = new CharArrayBuffer(16); buffer.append("stuff"); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> buffer.subSequence(-2, 10)); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/util/TestDeadline.java b/httpcore5/src/test/java/org/apache/hc/core5/util/TestDeadline.java index aba0c147d9..9677ad107d 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/util/TestDeadline.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/util/TestDeadline.java @@ -35,24 +35,24 @@ /** * Tests {@link Deadline}. */ -public class TestDeadline { +class TestDeadline { @Test - public void testFormat() throws ParseException { + void testFormat() throws ParseException { final Deadline deadline = Deadline.fromUnixMilliseconds(1000); final Deadline deadline2 = Deadline.parse(deadline.toString()); Assertions.assertEquals(1000, deadline2.getValue()); } @Test - public void testIsBefore() { + void testIsBefore() { final long nowPlusOneMin = System.currentTimeMillis() + 60000; final Deadline deadline = Deadline.fromUnixMilliseconds(nowPlusOneMin); Assertions.assertTrue(deadline.isBefore(nowPlusOneMin + 1)); } @Test - public void testIsExpired() { + void testIsExpired() { Assertions.assertTrue(Deadline.fromUnixMilliseconds(0).isExpired()); Assertions.assertTrue(Deadline.fromUnixMilliseconds(1).isExpired()); Assertions.assertFalse(Deadline.MAX_VALUE.isExpired()); @@ -60,7 +60,7 @@ public void testIsExpired() { } @Test - public void testIsMax() { + void testIsMax() { Assertions.assertFalse(Deadline.fromUnixMilliseconds(0).isMax()); Assertions.assertFalse(Deadline.fromUnixMilliseconds(1000).isMax()); Assertions.assertFalse(Deadline.MIN_VALUE.isMax()); @@ -68,7 +68,7 @@ public void testIsMax() { } @Test - public void testIsMin() { + void testIsMin() { Assertions.assertTrue(Deadline.fromUnixMilliseconds(0).isMin()); Assertions.assertFalse(Deadline.fromUnixMilliseconds(1000).isMin()); Assertions.assertFalse(Deadline.MAX_VALUE.isMin()); @@ -76,7 +76,7 @@ public void testIsMin() { } @Test - public void testIsNotExpired() { + void testIsNotExpired() { Assertions.assertFalse(Deadline.fromUnixMilliseconds(0).isNotExpired()); Assertions.assertFalse(Deadline.fromUnixMilliseconds(1).isNotExpired()); Assertions.assertTrue(Deadline.MAX_VALUE.isNotExpired()); @@ -84,7 +84,7 @@ public void testIsNotExpired() { } @Test - public void testMin() { + 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)); // @@ -98,13 +98,13 @@ public void testMin() { } @Test - public void testParse() throws ParseException { + void testParse() throws ParseException { final Deadline deadline = Deadline.parse("1969-12-31T17:00:01.000-0700"); Assertions.assertEquals(1000, deadline.getValue()); } @Test - public void testRemaining() { + void testRemaining() { final int oneHourInMillis = 60_000 * 60; final long nowPlusOneHour = System.currentTimeMillis() + oneHourInMillis; final Deadline deadline = Deadline.fromUnixMilliseconds(nowPlusOneHour); @@ -114,7 +114,7 @@ public void testRemaining() { } @Test - public void testRemainingTimeValue() { + void testRemainingTimeValue() { final int oneHourInMillis = 60_000 * 60; final long nowPlusOneHour = System.currentTimeMillis() + oneHourInMillis; final Deadline deadline = Deadline.fromUnixMilliseconds(nowPlusOneHour); @@ -125,7 +125,7 @@ public void testRemainingTimeValue() { } @Test - public void testValue() { + void testValue() { final long nowPlusOneMin = System.currentTimeMillis() + 60000; final Deadline deadline = Deadline.fromUnixMilliseconds(nowPlusOneMin); Assertions.assertEquals(nowPlusOneMin, deadline.getValue()); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/util/TestDeadlineTimeoutException.java b/httpcore5/src/test/java/org/apache/hc/core5/util/TestDeadlineTimeoutException.java index 401098b6a2..d13a4d57f3 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/util/TestDeadlineTimeoutException.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/util/TestDeadlineTimeoutException.java @@ -30,10 +30,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestDeadlineTimeoutException { +class TestDeadlineTimeoutException { @Test - public void testMessage() { + void testMessage() { final Deadline deadline = Deadline.fromUnixMilliseconds(1000).freeze(); Assertions.assertTrue(deadline.isExpired(), deadline.toString()); final String format = deadline.formatTarget(); @@ -43,7 +43,7 @@ public void testMessage() { } @Test - public void testInfiniteDeadlineMessage() { + void testInfiniteDeadlineMessage() { final Deadline deadline = Deadline.calculate(Timeout.ZERO_MILLISECONDS); Assertions.assertEquals("No deadline (infinite)", DeadlineTimeoutException.from(deadline).getMessage()); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/util/TestLangUtils.java b/httpcore5/src/test/java/org/apache/hc/core5/util/TestLangUtils.java index c302b56bde..d95ecb1df2 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/util/TestLangUtils.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/util/TestLangUtils.java @@ -34,10 +34,10 @@ * Unit tests for {@link LangUtils}. * */ -public class TestLangUtils { +class TestLangUtils { @Test - public void testBasicHash() { + 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); @@ -45,19 +45,19 @@ public void testBasicHash() { } @Test - public void testNullObjectHash() { + 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 - public void testBooleanHash() { + 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.assertTrue(h1 != h2); + Assertions.assertNotEquals(h1, h2); Assertions.assertEquals(h1, h3); Assertions.assertEquals(h2, h4); } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/util/TestTextUtils.java b/httpcore5/src/test/java/org/apache/hc/core5/util/TestTextUtils.java index b3770ba300..67cda2d25d 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/util/TestTextUtils.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/util/TestTextUtils.java @@ -34,17 +34,17 @@ * Unit tests for {@link TextUtils}. * */ -public class TestTextUtils { +class TestTextUtils { @Test - public void testTextEmpty() { + void testTextEmpty() { Assertions.assertTrue(TextUtils.isEmpty(null)); Assertions.assertTrue(TextUtils.isEmpty("")); Assertions.assertFalse(TextUtils.isEmpty("\t")); } @Test - public void testTextBlank() { + void testTextBlank() { Assertions.assertTrue(TextUtils.isBlank(null)); Assertions.assertTrue(TextUtils.isBlank("")); Assertions.assertTrue(TextUtils.isBlank(" ")); @@ -52,7 +52,7 @@ public void testTextBlank() { } @Test - public void testTextContainsBlanks() { + void testTextContainsBlanks() { Assertions.assertFalse(TextUtils.containsBlanks(null)); Assertions.assertFalse(TextUtils.containsBlanks("")); Assertions.assertTrue(TextUtils.containsBlanks(" ")); @@ -62,7 +62,7 @@ public void testTextContainsBlanks() { } @Test - public void testToHexString() { + void testToHexString() { Assertions.assertEquals("000c2001ff", TextUtils.toHexString(new byte[] { 0, 12, 32, 1 , -1})); Assertions.assertNull(TextUtils.toHexString(null)); } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/util/TestTimeValue.java b/httpcore5/src/test/java/org/apache/hc/core5/util/TestTimeValue.java index f1b2a5c1a3..9c2a1b8ee2 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/util/TestTimeValue.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/util/TestTimeValue.java @@ -37,7 +37,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestTimeValue { +class TestTimeValue { private void checkToDays(final long value, final TimeUnit timeUnit) { Assertions.assertEquals(timeUnit.toDays(value), TimeValue.of(value, timeUnit).toDays()); @@ -80,23 +80,23 @@ private void test(final long value) { } @Test - public void test0() { + void test0() { test(0); } @Test - public void test1() { + void test1() { test(1); } @Test - public void testConvert() { + void testConvert() { Assertions.assertEquals(0, TimeValue.ofMilliseconds(0).convert(TimeUnit.DAYS)); Assertions.assertEquals(1000, TimeValue.ofSeconds(1).convert(TimeUnit.MILLISECONDS)); } @Test - public void testDivide() { + 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()); @@ -115,7 +115,7 @@ public void testDivide() { } @Test - public void testDivideBy0() { + void testDivideBy0() { Assertions.assertThrows(ArithmeticException.class, () -> TimeValue.ofMilliseconds(0).divide(0)); } @@ -128,12 +128,12 @@ private void testFactory(final TimeUnit timeUnit) { } @Test - public void testFactoryForDays() { + void testFactoryForDays() { testFactory(TimeUnit.DAYS); } @Test - public void testFactoryForDuration() { + void testFactoryForDuration() { assertConvertion(Duration.ZERO); assertConvertion(Duration.ofDays(1)); assertConvertion(Duration.ofHours(1)); @@ -148,37 +148,37 @@ private void assertConvertion(final Duration duration) { } @Test - public void testFactoryForHours() { + void testFactoryForHours() { testFactory(TimeUnit.HOURS); } @Test - public void testFactoryForMicroseconds() { + void testFactoryForMicroseconds() { testFactory(TimeUnit.MICROSECONDS); } @Test - public void testFactoryForMilliseconds() { + void testFactoryForMilliseconds() { testFactory(TimeUnit.MILLISECONDS); } @Test - public void testFactoryForMinutes() { + void testFactoryForMinutes() { testFactory(TimeUnit.MINUTES); } @Test - public void testFactoryForNanoseconds() { + void testFactoryForNanoseconds() { testFactory(TimeUnit.NANOSECONDS); } @Test - public void testFactoryForSeconds() { + void testFactoryForSeconds() { testFactory(TimeUnit.SECONDS); } @Test - public void testMin() { + void testMin() { final TimeValue nanos1 = TimeValue.ofNanoseconds(1); final TimeValue micros1 = TimeValue.ofMicroseconds(1); final TimeValue millis1 = TimeValue.ofMilliseconds(1); @@ -253,28 +253,28 @@ public void testMin() { } @Test - public void testMaxInt() { + void testMaxInt() { test(Integer.MAX_VALUE); } @Test - public void testMaxLong() { + void testMaxLong() { test(Long.MAX_VALUE); } @Test - public void testNegative1() { + void testNegative1() { test(-1); } @Test - public void testToString() { + void testToString() { Assertions.assertEquals("9223372036854775807 SECONDS", TimeValue.ofSeconds(Long.MAX_VALUE).toString()); Assertions.assertEquals("0 MILLISECONDS", TimeValue.ZERO_MILLISECONDS.toString()); } @Test - public void testFromString() throws ParseException { + 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")); @@ -287,12 +287,12 @@ public void testFromString() throws ParseException { } @Test - public void testToDuration() throws ParseException { + void testToDuration() throws ParseException { Assertions.assertEquals(Long.MAX_VALUE, TimeValue.parse("9223372036854775807 SECONDS").toDuration().getSeconds()); } @Test - public void testEqualsAndHashCode() { + void testEqualsAndHashCode() { final TimeValue tv1 = TimeValue.ofMilliseconds(1000L); final TimeValue tv2 = TimeValue.ofMilliseconds(1001L); final TimeValue tv3 = TimeValue.ofMilliseconds(1000L); @@ -315,7 +315,7 @@ public void testEqualsAndHashCode() { } @Test - public void testCompareTo() { + void testCompareTo() { final TimeValue tv1 = TimeValue.ofMilliseconds(1000L); final TimeValue tv2 = TimeValue.ofMilliseconds(1001L); final TimeValue tv3 = TimeValue.ofMilliseconds(1000L); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/util/TestTimeout.java b/httpcore5/src/test/java/org/apache/hc/core5/util/TestTimeout.java index 6168a65949..6c96bdaf36 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/util/TestTimeout.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/util/TestTimeout.java @@ -34,7 +34,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestTimeout { +class TestTimeout { private void checkToDays(final long value, final TimeUnit timeUnit) { Assertions.assertEquals(timeUnit.toDays(value), Timeout.of(value, timeUnit).toDays()); @@ -77,17 +77,17 @@ private void test(final long value) { } @Test - public void test0() { + void test0() { test(0); } @Test - public void test1() { + void test1() { test(1); } @Test - public void testDisabled() { + void testDisabled() { Assertions.assertTrue(Timeout.DISABLED.isDisabled()); Assertions.assertFalse(Timeout.DISABLED.isEnabled()); } @@ -97,12 +97,12 @@ private void testFactory(final TimeUnit timeUnit) { } @Test - public void testFactoryForDays() { + void testFactoryForDays() { testFactory(TimeUnit.DAYS); } @Test - public void testFactoryForDuration() { + void testFactoryForDuration() { assertConvertion(Duration.ZERO); assertConvertion(Duration.ofDays(1)); assertConvertion(Duration.ofHours(1)); @@ -117,59 +117,59 @@ private void assertConvertion(final Duration duration) { } @Test - public void testFactoryForHours() { + void testFactoryForHours() { testFactory(TimeUnit.HOURS); } @Test - public void testFactoryForMicroseconds() { + void testFactoryForMicroseconds() { testFactory(TimeUnit.MICROSECONDS); } @Test - public void testFactoryForMillisseconds() { + void testFactoryForMillisseconds() { testFactory(TimeUnit.MILLISECONDS); } @Test - public void testFactoryForMinutes() { + void testFactoryForMinutes() { testFactory(TimeUnit.MINUTES); } @Test - public void testFactoryForNanoseconds() { + void testFactoryForNanoseconds() { testFactory(TimeUnit.NANOSECONDS); } @Test - public void testFactoryForSeconds() { + void testFactoryForSeconds() { testFactory(TimeUnit.SECONDS); } @Test - public void testMaxInt() { + void testMaxInt() { test(Integer.MAX_VALUE); } @Test - public void testMaxLong() { + void testMaxLong() { test(Long.MAX_VALUE); } @Test - public void testNegative1() { + void testNegative1() { Assertions.assertThrows(IllegalArgumentException.class, () -> test(-1)); } @Test - public void testToString() { + void testToString() { Assertions.assertEquals("9223372036854775807 SECONDS", Timeout.ofSeconds(Long.MAX_VALUE).toString()); Assertions.assertEquals("0 MILLISECONDS", Timeout.ZERO_MILLISECONDS.toString()); } @Test - public void testFromString() throws ParseException { + 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")); diff --git a/httpcore5/src/test/java/org/apache/hc/core5/util/TestTimeoutValueException.java b/httpcore5/src/test/java/org/apache/hc/core5/util/TestTimeoutValueException.java index d885790b1a..bf67891d5e 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/util/TestTimeoutValueException.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/util/TestTimeoutValueException.java @@ -30,10 +30,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class TestTimeoutValueException { +class TestTimeoutValueException { @Test - public void testMessage() { + void testMessage() { Assertions.assertEquals("Timeout deadline: 1000 MILLISECONDS, actual: 2000 MILLISECONDS", TimeoutValueException.fromMilliseconds(1000, 2000).getMessage()); } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/util/TestTokenizer.java b/httpcore5/src/test/java/org/apache/hc/core5/util/TestTokenizer.java index ce0dfd5069..5038f2922c 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/util/TestTokenizer.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/util/TestTokenizer.java @@ -31,12 +31,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class TestTokenizer { +class TestTokenizer { private Tokenizer parser; @BeforeEach - public void setUp() throws Exception { + void setUp() { parser = new Tokenizer(); } @@ -50,7 +50,7 @@ private static CharArrayBuffer createBuffer(final String value) { } @Test - public void testBasicTokenParsing() throws Exception { + void testBasicTokenParsing() { final String s = " raw: \" some stuff \""; final CharArrayBuffer raw = createBuffer(s); final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); @@ -61,7 +61,7 @@ public void testBasicTokenParsing() throws Exception { Assertions.assertEquals(3, cursor.getPos()); final StringBuilder strbuf1 = new StringBuilder(); - parser.copyContent(raw, cursor, Tokenizer.INIT_BITSET(':'), strbuf1); + parser.copyContent(raw, cursor, Tokenizer.delimiters(':'), strbuf1); Assertions.assertFalse(cursor.atEnd()); Assertions.assertEquals(6, cursor.getPos()); @@ -88,7 +88,7 @@ public void testBasicTokenParsing() throws Exception { } @Test - public void testTokenParsingWithQuotedPairs() throws Exception { + void testTokenParsingWithQuotedPairs() { final String s = "raw: \"\\\"some\\stuff\\\\\""; final CharArrayBuffer raw = createBuffer(s); final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); @@ -99,7 +99,7 @@ public void testTokenParsingWithQuotedPairs() throws Exception { Assertions.assertEquals(0, cursor.getPos()); final StringBuilder strbuf1 = new StringBuilder(); - parser.copyContent(raw, cursor, Tokenizer.INIT_BITSET(':'), strbuf1); + parser.copyContent(raw, cursor, Tokenizer.delimiters(':'), strbuf1); Assertions.assertFalse(cursor.atEnd()); Assertions.assertEquals("raw", strbuf1.toString()); @@ -118,7 +118,7 @@ public void testTokenParsingWithQuotedPairs() throws Exception { } @Test - public void testTokenParsingIncompleteQuote() throws Exception { + void testTokenParsingIncompleteQuote() { final String s = "\"stuff and more stuff "; final CharArrayBuffer raw = createBuffer(s); final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); @@ -128,65 +128,65 @@ public void testTokenParsingIncompleteQuote() throws Exception { } @Test - public void testTokenParsingTokensWithUnquotedBlanks() throws Exception { + 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.INIT_BITSET(';')); + final String result = parser.parseToken(raw, cursor, Tokenizer.delimiters(';')); Assertions.assertEquals("stuff and some more stuff", result); } @Test - public void testTokenParsingMixedValuesAndQuotedValues() throws Exception { + 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.INIT_BITSET(';')); + final String result = parser.parseValue(raw, cursor, Tokenizer.delimiters(';')); Assertions.assertEquals("stuff and some more stuff ;", result); } @Test - public void testTokenParsingMixedValuesAndQuotedValues2() throws Exception { + 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.INIT_BITSET(';')); + final String result = parser.parseValue(raw, cursor, Tokenizer.delimiters(';')); Assertions.assertEquals("stuffmorestuff", result); } @Test - public void testTokenParsingEscapedQuotes() throws Exception { + 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.INIT_BITSET(';')); + final String result = parser.parseValue(raw, cursor, Tokenizer.delimiters(';')); Assertions.assertEquals("stuff\"more\"stuff", result); } @Test - public void testTokenParsingEscapedDelimiter() throws Exception { + 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.INIT_BITSET(';')); + final String result = parser.parseValue(raw, cursor, Tokenizer.delimiters(';')); Assertions.assertEquals("stuff\"more\";stuff", result); } @Test - public void testTokenParsingEscapedSlash() throws Exception { + 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.INIT_BITSET(';')); + final String result = parser.parseValue(raw, cursor, Tokenizer.delimiters(';')); Assertions.assertEquals("stuff\"more\";\\stuff", result); } @Test - public void testTokenParsingSlashOutsideQuotes() throws Exception { + 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.INIT_BITSET(';')); + final String result = parser.parseValue(raw, cursor, Tokenizer.delimiters(';')); Assertions.assertEquals("stuff\\", result); } } diff --git a/pom.xml b/pom.xml index d4b81afd15..119fbcc513 100644 --- a/pom.xml +++ b/pom.xml @@ -33,9 +33,9 @@ org.apache.httpcomponents.core5 httpcore5-parent Apache HttpComponents Core Parent - 5.2.4-SNAPSHOT + 5.3 Apache HttpComponents Core is a library of components for building HTTP enabled services - https://hc.apache.org/httpcomponents-core-5.2.x/${project.version}/ + https://hc.apache.org/httpcomponents-core-5.3.x/${project.version}/ 2005 pom @@ -48,14 +48,14 @@ 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.2.4-SNAPSHOT + 5.3 apache.website Apache HttpComponents Website - scm:svn:https://svn.apache.org/repos/asf/httpcomponents/site/components/httpcomponents-core-5.2.x/LATEST/ + scm:svn:https://svn.apache.org/repos/asf/httpcomponents/site/components/httpcomponents-core-5.3.x/LATEST/ @@ -72,14 +72,14 @@ 1.8 true 2.5.2 - 5.9.3 - 2.2 + 5.11.0 + 3.0 5.0.0 4.11.0 1.7.36 - 2.19.0 + 2.23.1 2.2.21 - 3.1.6 + 3.1.9 5.2 javax.net.ssl.SSLEngine,javax.net.ssl.SSLParameters,java.nio.ByteBuffer,java.nio.CharBuffer @@ -106,6 +106,12 @@ httpcore5-testing ${project.version} + + org.apache.httpcomponents.core5 + httpcore5 + tests + ${project.version} + org.conscrypt conscrypt-openjdk-uber